严格次小生成树
题目描述
小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的。
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。
输入格式
第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。
输出格式
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
洛谷P4180
分析:看到这个题的第一眼想到的是枚举,把所有生成树枚举一遍求最小的,但是显然不行,因为数据有些大了。
想一下最小生成树的算法?Kurskal是通过每一次加最小边来实现最小树,所以我们考虑一下边。
对于一棵最小生成树而言,删去树上一边,再加上一条边维护树,就有可能构成次小生成树,为保证它是严格的,所以还要再判断这条边与删去的边是否相等。
现在我们考虑删边,如何保证删去边再加上边仍然是树呢?显然先加边,然后看这条边与其他边构成的环中,最大的那条边是多少,为什么要用最大边呢,因为要求的是次小生成树,在一个点附近,加完边后剩下的边一定大于等于加上的边,不然根据Kurskal不会把这条边加上,但这条边还有可能和加的边相等,所以在记录最大边的同时还要记录次大边。
删边和加边的问题解决了,接下来就是记录了,关于记录,使用lca是我没有想到的。
为什么要用lca呢?加边后构成的环,不考虑加的这条边的话,可以分成两部分,一是从边的from到lca,二是从边的to到lca,所以在跑倍增lca的同时(顺便)维护一下最大值和次大值。
最后就是计算了,计算的时候也是同上分两部分,记得判断最大值是不是和边权一样,如果是的话要用次大值。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=1e5+10; 6 //前向星存边 7 struct Edge{ 8 int to,from,next,val; 9 bool isin; 10 Edge(){isin=val=next=0;} 11 bool operator < (const Edge &A)const { 12 return val<A.val; 13 } 14 }e[N<<1],E[N*3]; 15 int len,Head[N]; 16 void Ins(int a,int b,int c){ 17 e[++len].to=b;e[len].val=c; 18 e[len].next=Head[a];Head[a]=len; 19 } 20 //并查集+Kurskal 21 int f[N],m,n; 22 int find(int x){ 23 return f[x]==x?x:(f[x]=find(f[x])); 24 } 25 int ans=0x3f3f3f3f;long long tot=0; 26 void Krs(){ 27 int cnt=1; 28 sort(E+1,E+m+1); 29 for(int i=1;cnt<n;i++){ 30 int v=E[i].to,u=E[i].from; 31 if(find(v)!=find(u)){ 32 cnt++; 33 E[i].isin=1; 34 tot+=E[i].val;//计算最小生成树 35 Ins(u,v,E[i].val); 36 Ins(v,u,E[i].val); 37 f[find(v)]=find(u); 38 } 39 } 40 } 41 //倍增lca板子 42 int dep[N],p[N][20],Max[N][20],Smax[N][20]; 43 void dfs(int x){ 44 for(int i=0;p[x][i];i++){ 45 p[x][i+1]=p[p[x][i]][i]; 46 Max[x][i+1]=max(Max[x][i],Max[p[x][i]][i]); 47 //注意求次大的时候看看两段的最大值是不是相等,如果不判断的话 48 //在最大值相等的时候,次大值会被更新为最大值 49 if(Max[x][i]==Max[p[x][i]][i]) 50 Smax[x][i+1]=max(Smax[x][i],Smax[p[x][i]][i]); 51 else 52 Smax[x][i+1]=max(min(Max[x][i],Max[p[x][i]][i]), 53 max(Smax[x][i],Smax[p[x][i]][i])); 54 } 55 for(int i=Head[x];i;i=e[i].next){ 56 int v=e[i].to; 57 if(v!=p[x][0]){ 58 dep[v]=dep[x]+1; 59 Max[v][0]=e[i].val; 60 Smax[v][0]=-1; 61 p[v][0]=x; 62 dfs(v); 63 } 64 } 65 } 66 //这段和lca板子一模一样 67 int lca(int a,int b){ 68 if(dep[a]<dep[b])swap(a,b); 69 int d=dep[a]-dep[b]; 70 for(int i=0;d;i++,d>>=1) 71 if(d&1)a=p[a][i]; 72 if(a==b)return a; 73 for(int i=18;i>=0;i--) 74 if(p[a][i]!=p[b][i]) 75 a=p[a][i],b=p[b][i]; 76 return p[a][0]; 77 } 78 //计算 79 void calc(int u,int v,int w){ 80 int d=dep[u]-dep[v];//深度差,判断路径 81 int m1=0,m2=0; 82 for(int i=0;d;i++,d>>=1){ 83 if(d&1){//如果可以往上跳 84 m2=max(m2,Smax[u][i]);//细节,先求次大值 85 if(Max[u][i]>m1){//如果更新最大值,那么原来的最大值m1可能为新的次大值 86 m2=max(m2,m1);//判断次大值是否更新 87 m1=Max[u][i];//更新最大值 88 } 89 } 90 } 91 if(m1==w)ans=min(ans,w-m2);//如果最大值与边相等,用次大值更新 92 else ans=min(ans,w-m1);//否则用最大值 93 } 94 int main(){ 95 // freopen("a.txt","r",stdin); 96 scanf("%d%d",&n,&m); 97 for(int i=1;i<=n;i++) 98 f[i]=i; 99 for(int i=1;i<=m;i++) 100 scanf("%d%d%d",&E[i].from,&E[i].to,&E[i].val); 101 Krs(); 102 dfs(1); 103 for(int i=1;i<=m;i++){ 104 if(!E[i].isin){ 105 int u=E[i].from,v=E[i].to,Lca; 106 Lca=lca(u,v); 107 calc(u,Lca,E[i].val); 108 calc(v,Lca,E[i].val); 109 } 110 } 111 printf("%lld\n",tot+ans); 112 }