题意:给定一个带权边无向图,求最小生成树,且满足第一个节点的度为固定的k 无解则输出-1
数据规模: 节点数n和限制k<=5000 边数m<=10^5 时限8sec
思路:
首先时限比较宽,第一个想到的暴力做法是枚举第一个节点(即首都)选中的K条边,复杂度为阶乘级别 无法接受。但是我们可以确定,问题的关键在于在所有国道中选择哪k条国道。一旦k条国道选定,最小生成树就是固定的。
考虑一个极端的情况,如果我们直接对这个图运行Kruskal算法,得到了一个树 且 第一个节点的度为k,则这就是正确的答案。当然,一般数据是无法做到的。
那么我们就可以使用一个技巧,如果运行Kruskal算法后第一个节点的度小于k,那么我们在算法运行的序列中将所有国道的位置整体向前移动,使得下一次运行Kruskal算法可以选定更多的国道,我们就离正确答案更近了。反之亦然。
那么怎样调整所有“国道”在Kruskal序列中的位置呢?
选定一个double值mid 它可以很大,也可以很小,在排序时“国道”的权值加上这个mid再与非国道比较,就可以适当的调节所有“国道”在序列中的位置。
代码如下,只要理解了排序比较函数cmp 也就理解了这个Kruskal的变种算法了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cmath> 5 #include<queue> 6 #include<stack> 7 #include<map> 8 #include<algorithm> 9 using namespace std; 10 const int maxn=5000+1,maxm=100000+1; 11 int x[maxm],y[maxm],w[maxm],sig[maxm],ance[maxn],ans[maxn]; 12 int n,m,k,tot,cnt,captedge; 13 double l,r,mid; 14 bool cmp(int a,int b) 15 { 16 return (w[a]+(x[a]==1)*mid)<(w[b]+(x[b]==1)*mid);//这个算法的核心 17 } 18 int findance(int x) 19 { 20 if(x==ance[x])return x; 21 else return ance[x]=findance(ance[x]); 22 } 23 void Kruskal(bool flag) 24 { 25 cnt=captedge=0; 26 for(int i=1;i<=n;i++) 27 { 28 ance[i]=i; 29 } 30 sort(sig,sig+m,cmp); 31 for(int i=0;i<m;i++) 32 { 33 if(cnt+1==n) return ; 34 int j=sig[i]; 35 int u=findance(x[j]),v=findance(y[j]); 36 if((u!=v)&&(flag||(captedge+(x[j]==1))<=k)) 37 38 { 39 ance[u]=v; 40 ans[cnt++]=j; 41 if(x[j]==1)captedge++; 42 } 43 } 44 } 45 int main() 46 { 47 ios::sync_with_stdio(false); 48 freopen("t.txt","r",stdin); 49 cin>>n>>m>>k; 50 tot=0; 51 for(int i=0;i<m;i++) 52 { 53 sig[i]=i; 54 cin>>x[i]>>y[i]>>w[i]; 55 if(x[i]>y[i]){int t=x[i];x[i]=y[i];y[i]=t;}//swap 56 if(x[i]==1)tot++; 57 } 58 if((k>tot)||(k==0&&n>1)) 59 { 60 cout<<"-1"<<endl; 61 return 0; 62 } 63 cnt=0; 64 Kruskal(1); 65 if(cnt!=(n-1)) 66 { 67 cout<<"-1"<<endl; 68 return 0; 69 } 70 l=(-1)*100000.0; 71 r=100000.0; 72 while(((l+1e-5)<r)&&(captedge!=k)) 73 { 74 mid=(l+r)/2.0; 75 Kruskal(1); 76 if(captedge<k)r=mid; 77 else l=mid; 78 } 79 if(captedge!=k)mid=(l+r)/2.0; 80 Kruskal(0); 81 cout<<n-1<<endl; 82 if(cnt>=1)cout<<ans[0]+1; 83 for(int i=1;i<cnt;i++) 84 cout<<" "<<ans[i]+1; 85 cout<<endl; 86 return 0; 87 }