暑假模擬19
\(T_A\) 數字三角形
簡單模擬,做法眾多。
多數人都是緊貼一邊填充,但我是每次每個數只填充一位,每次有一個數填夠,剛好填滿。
\(T_B\) 那一天她離我而去
找最小環模板題。
做法一
神奇的二進位制分組。考慮與1相連的所有節點,一個是起點,一個是終點。一個暴力想法就是,列舉起點,斷開這條邊,跑 dijkstra
,複雜度 $ O( n^2 \ \log n ) $ 。最佳化,列舉二進位制位數,按二進位制位分成兩組,建立超級源點,一組與超級源點連邊,並斷開與1相鄰的邊,因為不同整數在二進位制下至少有一位不同,所以任取一對節點,一定會被分到不同組,正確性顯然,複雜度 $ O( n\ \log^2 n ) $
CODE
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
const int inf=1e8;
int T,n,m,a,b,c,cnt,head[N],d[N];
struct edge{
int to,nxt,val;
}e[N<<5];
vector<int>son,sec;
int dis[N],ans;
bool vis[N<<5];
inline void add_edge(int u,int v,int w){
cnt++;
e[cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].val=w;
head[u]=cnt;
}
struct node{
int id,dis;
bool operator<(node x)const{
return dis>x.dis;
}
};
priority_queue<node>q;
void dijkstra(){
q.push({n+1,0});
dis[n+1]=0;
while(!q.empty()){
node x=q.top();
q.pop();
for(int i=head[x.id];i;i=e[i].nxt){
if(vis[i])continue;
int v=e[i].to;
if(dis[v]>dis[x.id]+e[i].val){
dis[v]=dis[x.id]+e[i].val;
q.push({v,dis[v]});
}
}
}
}
int main()
{
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
ans=inf;
memset(d,0x3f,sizeof(d));
son.clear();
cnt=0;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
add_edge(a,b,c);
add_edge(b,a,c);
if(a==1){
d[b]=min(d[b],c);
vis[cnt]=1;
vis[cnt-1]=1;
son.push_back(b);
}
if(b==1){
vis[cnt]=1;
vis[cnt-1]=1;
d[a]=min(d[a],c);
son.push_back(a);
}
}
for(int i=0;i<=15;i++){
int num=cnt;
for(auto j:son){
if(j&(1<<i)){
add_edge(n+1,j,d[j]);
}
else {
sec.push_back(j);
}
}
memset(dis,0x3f,sizeof(dis));
dijkstra();
for(auto j:sec){
ans=min(ans,dis[j]+d[j]);
}
sec.clear();
for(int j=num+1;j<=cnt;j++){
vis[j]=1;
}
}
if(ans<inf)printf("%d\n",ans);
else puts("-1");
}
}
做法二
使用貪心的思想。先預處理跑 dijkstra
,並處理出最短路樹,記錄在樹上每個節點屬於哪個1的直接子節點的子樹。列舉每條邊,如果邊連線的兩點不在同一子樹,那麼它們就可能是最小環的一部分,統計答案。複雜度 $ O( n \ \log n ) $
CODE
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
const int inf=1e8;
int T,n,m,a,b,c,cnt,head[N],d[N],fa[N],root[N],fir[N],sec[N],wei[N];
struct edge{
int to,nxt,val;
}e[N<<5];
vector<int>son[N];
int dis[N],ans;
bool vis[N<<5];
inline void add_edge(int u,int v,int w){
cnt++;
e[cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].val=w;
head[u]=cnt;
}
struct node{
int id,dis;
bool operator<(node x)const{
return dis>x.dis;
}
};
priority_queue<node>q;
void dijkstra(){
q.push({1,0});
dis[1]=0;
while(!q.empty()){
node x=q.top();
q.pop();
for(int i=head[x.id];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[x.id]+e[i].val){
fa[v]=x.id;
dis[v]=dis[x.id]+e[i].val;
q.push({v,dis[v]});
}
}
}
}
void init(){
cnt=0;
ans=inf;
memset(head,0,sizeof(head));
memset(fa,0,sizeof(fa));
memset(dis,0x3f,sizeof(dis));
memset(root,0,sizeof(root));
for(int i=1;i<=n;i++){
son[i].clear();
}
}
void dfs(int rt,int u){
root[u]=rt;
for(auto v:son[u]){
dfs(rt,v);
}
}
int main()
{
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
fir[i]=a;
sec[i]=b;
wei[i]=c;
add_edge(a,b,c);
add_edge(b,a,c);
}
dijkstra();
for(int i=2;i<=n;i++){
son[fa[i]].push_back(i);
}
for(auto i:son[1]){
dfs(i,i);
}
for(int i=1;i<=m;i++){
if(root[fir[i]]!=root[sec[i]]&&fa[fir[i]]!=sec[i]&&fa[sec[i]]!=fir[i]){
ans=min(ans,dis[fir[i]]+dis[sec[i]]+wei[i]);
}
}
if(ans<inf)printf("%d\n",ans);
else puts("-1");
}
}
\(T_C\) 哪一天她能重回我身邊
很容易想到建雙向邊,並記錄這條邊初始時的方向,那麼圖會被分為若干個連通塊,分開處理。認為邊的起點是卡牌朝上的那面,要想合法,當且僅當每個點的入度不超過1。設該連通塊中 \(n\) 為點數, \(m\) 為邊數。若 $ m>n $ ,顯然無解;若 $ m=n $ ,是個基環樹,環上節點一定首尾相連,只有兩種情況;若 $ m=n+1 $ ,是棵樹,一個節點入度為 \(0\) ,其餘入度為 \(1\) ,考慮哪個節點入度是 \(0\) ,換根DP做即可。
\(T_D\) 單調區間
DP做法。
設 $ dp[i][0] $ 為第 \(i\) 位屬於遞增區間,遞減區間末尾元素的最大值, $ dp[i][1] $ 同理。
考慮暴力,列舉左端點,轉移顯然,複雜度 $ O( n^2 ) $
神奇最佳化。考慮一個神奇性質: $ dp[i][0/1] $ 的取值只有 \(4\) 個。
證明:
以 $ dp[i][0] $ 為例。對於第 \(i\) 位最大化一個 $ j<i $ 使得 $ p_j>p_{ j+1 } $ ,那麼 $ p_j $ 和 $ p_{ j+1 } $ 不能同時在遞增序列中,同時根據 $ dp[i][0] $ 的定義,可以得出,可能取值有 $ p_j,p_{ j+1 },-\infty $ ,當不存在這個 $ j $ 時,取值為 $ +\infty $。 $ dp[i][1] $ 同理。
由於可能的取值很少,我們可以記憶化,複雜度做到 $ O(n) $
有神奇的樹狀陣列和不確定複雜度的二分做法,這裡不展開。