生成树
即使是基础的算法也能出很难的题啊。
先来一个板子题:
[模板]最小生成树:https://www.luogu.org/problemnew/show/P3366
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 5 using namespace std; 6 7 int G[5002][5002]={0},M[5002]={0}; 8 bool f[5002]={false}; 9 10 int main() 11 { 12 13 int n,m,x,y,z; 14 memset(G,0x7f,sizeof(G)); 15 scanf("%d%d",&n,&m); 16 for (int i=1;i<=m;i++) 17 { 18 scanf("%d%d%d",&x,&y,&z); 19 if (G[x][y]>=z) 20 G[x][y]=G[y][x]=z; 21 } 22 memset(M,0x7f,sizeof(M)); 23 M[1]=0; 24 memset(f,1,sizeof(f)); 25 for (int i=1;i<=n;i++) 26 { 27 int k=0; 28 for (int j=1;j<=n;j++) 29 if ((f[j]!=0)&&(M[j]<M[k])) k=j; 30 f[k]=false; 31 for (int j=1;j<=n;j++) 32 if (f[j]&&M[j]>G[k][j]) 33 M[j]=G[k][j]; 34 } 35 int A=0,S=0; 36 for (int i=1;i<=n;i++) 37 A+=f[i]; 38 if (A!=0) {printf("orz"); 39 return 0;} 40 for (int i=1;i<=n;i++) 41 S+=M[i]; 42 cout<<S; 43 return 0; 44 }
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <algorithm> 5 # define R register int 6 7 using namespace std; 8 9 int fx,fy,n,m,ans,S; 10 int F[5009]; 11 struct edge 12 { 13 int x,y,co; 14 }g[200009]; 15 16 bool cmp(edge a,edge b) 17 { 18 return a.co<b.co; 19 } 20 21 int father(int x) 22 { 23 if(x!=F[x]) return F[x]=father(F[x]); 24 return x; 25 } 26 27 int main() 28 { 29 scanf("%d%d",&n,&m); 30 S=n; 31 for (R i=1;i<=m;++i) 32 scanf("%d%d%d",&g[i].x,&g[i].y,&g[i].co); 33 sort(g+1,g+1+m,cmp); 34 for (R i=1;i<=n;++i) 35 F[i]=i; 36 for (R i=1;i<=m;++i) 37 { 38 fx=father(g[i].x); 39 fy=father(g[i].y); 40 if(fx!=fy) 41 F[fx]=fy,S--,ans+=g[i].co; 42 } 43 if(S!=1) 44 printf("orz"); 45 else 46 printf("%d",ans); 47 return 0; 48 }
$Prim$的时间复杂度:$O(V^{2})$;
$Kruskal$时间复杂度:$O(ElogE)$
虽然$Kruskal$用的比较多,但是在处理近乎完全图的时候还是$Prim$比较快($E=V^{2}$)
听说还有一种针对没有重复权值的图的算法:$Boruvka$,对于随机图来说$O(V+E)$,听起来还是很不错的,不过我觉得现在还没必要学这个。
爱的供养:https://www.luogu.org/problemnew/show/P2266
昨天做了NOI同步赛,赛后学了一种感觉很有用的Kruskal重构树,现在通过一道题来学习一下。
归程:https://www.luogu.org/problemnew/show/P4768
题意概述:给定一张无向图,每条边有长度和海拔,每次给定一个起点和水平面,要求开车走海拔高于水平面的边,再找一个地方下车走,最终到达1号,求走路这一部分的最小长度,强制在线。
先从一号点跑$Dijkstra$求出每一个点到一号点的最短路,这一步是不可能省略的。现在考虑暴力,每次读入起点后跑BFS,可以得到50分。考虑离线,按照每次的水平面进行排序,把高于水平面的点进行合并(并查集),在同一个并查集中的点说明可以开车互相到达,这个并查集的权值就是这一些点里面离一号最近的点的权值。说的好,但是强制在线啊。可持久化并查集听说有卡过的,然而我不会。
看看合并的顺序像不像一棵树呀。首先按照海拔排序,用kruskal的思路进行合并,每次将$f[u]$,$f[v]$连到一个新建的虚点上:点权为这条边的海拔,显然全部合并完后就会形成一棵树,这棵树就叫kruskal重构树。每次我们在这棵树上往上爬,当爬到最大的小于这次海平面的点时就说明如果海平面是这么大,这个点就只能合并到这里为止了。因为重构出的树的点权是单调的,可以树上倍增。如果学过这个算法的话这道题其实就是个模板,对于NOI选手应该是签到题?(听说这题卡SPFA)
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <cstring> 4 # include <iostream> 5 # include <queue> 6 # include <set> 7 # include <algorithm> 8 # define R register int 9 10 inline int min (int a,int b) { if(a<b) return a; return b; } 11 const int maxn=5e5+10; 12 const int maxm=2e6+10; 13 int cnt,T,n,m,h,da,k,s,fx,fy,u,v,l,a,p; 14 int kh,firs[maxn<<1],F[maxn<<1][22],f[maxn<<1],val[maxn<<1]; 15 bool vis[maxn<<1]; 16 long long d[maxn<<1],ans=0,mind[maxn<<1]; 17 typedef std::pair <long long,int> pii; 18 std::priority_queue <pii,std::vector<pii>,std::greater<pii> > q; 19 int firs_k[maxm<<1]; 20 struct edge 21 { 22 int too,nex,l,a; //l长度,a海拔 23 }g[maxm<<1]; 24 struct lin 25 { 26 int x,y,a; 27 }G[maxm]; 28 struct kru_tree 29 { 30 int nex,too; 31 }kru[maxm<<1]; 32 33 inline void add (int x,int y,int l,int a) 34 { 35 g[++h].too=y; 36 g[h].nex=firs[x]; 37 g[h].l=l; 38 g[h].a=a; 39 firs[x]=h; 40 } 41 42 inline void dij (int s) 43 { 44 memset(d,127,sizeof(d)); 45 memset(vis,0,sizeof(vis)); 46 d[s]=0; 47 while (q.size()) q.pop(); 48 q.push(std::make_pair(d[s],s)); 49 int j,beg; 50 while (q.size()) 51 { 52 beg=q.top().second; 53 q.pop(); 54 if(vis[beg]) continue; 55 vis[beg]=true; 56 for (R i=firs[beg];i;i=g[i].nex) 57 { 58 j=g[i].too; 59 if(d[beg]+g[i].l>=d[j]) continue; 60 d[j]=d[beg]+g[i].l; 61 q.push(std::make_pair(d[j],j)); 62 } 63 } 64 } 65 66 void add_edge(int x,int y) 67 { 68 kru[++kh].too=y; 69 kru[kh].nex=firs_k[x]; 70 firs_k[x]=kh; 71 } 72 73 bool cmp (lin a,lin b) 74 { 75 return a.a>b.a; 76 } 77 78 int father (int x) { return f[x]?f[x]=father(f[x]):x; } 79 80 void dfs (int x) 81 { 82 for (R i=firs_k[x];i;i=kru[i].nex) 83 dfs(kru[i].too),mind[x]=min(mind[x],mind[ kru[i].too]),F[ kru[i].too ][0]=x; 84 if(x<=n) mind[x]=d[x]; 85 } 86 87 int main() 88 { 89 scanf("%d",&T); 90 while (T--) 91 { 92 scanf("%d%d",&n,&m); 93 ans=0,cnt=n,kh=0; 94 memset(firs,0,sizeof(firs)); 95 memset(F,0,sizeof(F)); 96 memset(mind,127,sizeof(mind)); 97 memset(firs_k,0,sizeof(firs_k)); 98 memset(f,0,sizeof(f)); 99 memset(val,0,sizeof(val)); 100 h=0; 101 for (R i=1;i<=m;++i) 102 { 103 scanf("%d%d%d%d",&u,&v,&l,&a); 104 add(u,v,l,a); 105 add(v,u,l,a); 106 G[i].x=u; 107 G[i].y=v; 108 G[i].a=a; 109 } 110 dij(1); 111 std::sort(G+1,G+1+m,cmp); 112 for (R i=1;i<=m;++i) 113 { 114 fx=father(G[i].x); 115 fy=father(G[i].y); 116 if(fx==fy) continue; 117 f[fx]=f[fy]=++cnt; 118 add_edge(cnt,fx); 119 add_edge(cnt,fy); 120 val[cnt]=G[i].a; 121 } 122 dfs(cnt); 123 scanf("%d%d%d",&da,&k,&s); 124 for (int j=1;j<=21;++j) 125 for (int i=1;i<=cnt;++i) 126 F[i][j]=F[ F[i][j-1] ][j-1]; 127 for (R i=1;i<=da;++i) 128 { 129 scanf("%d%d",&v,&p); 130 v=(v+(long long)k*ans-1)%n+1; 131 p=(p+(long long)k*ans)%(s+1); 132 for (int j=21;j>=0;--j) 133 if(val[ F[v][j] ]>p) v=F[v][j]; 134 ans=mind[v]; 135 printf("%lld ",ans); 136 } 137 } 138 return 0; 139 }