P3623 [APIO2008] 免费道路
题目翻译:
给定一个无向联通图,图中的边有两种类型\(1\)或\(0\),求生成一棵树使得类型为\(0\)的边要为\(k\)个。
思路:
阅读题目发现,最后的图要是一棵树,使得上面边为\(0\)的个数为\(k\),我们可以想到先找到,\(k\)个边在找其他边,但又要满足图的联通,那我们运用最小生成树\(kruskal\)算法的思想,先将类型为\(1\)的边给进行加边,若继续加类型\(0\)的边,那这些边就是一定需要的,不管这么搭配,这些边就是必要的。那第二次跑\(kruskal\)时就先加剩下需要的类型为\(0\)的边,直到数量满足为\(k\)时停止,然后加类型为\(1\)的边直至联通。
无解判定:
1.若第一次跑\(kruskal\)时发现必须的边数超过了\(k\)那必然无解
2.若第二次跑完\(kruskal\)时发现无法组成一棵树,或总共加入的类型为零的边不及\(k\)个,则无解
完整代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int fa[N];
int n,m,k,cnt;
struct edge{int u,v,w;
}e[N];
bool cmp1(edge x,edge y){return x.w<y.w;
}
bool cmp2(edge x,edge y){return x.w>y.w;
}
int find(int x){if(fa[x]==x)return x;else return fa[x]=find(fa[x]);
}
void kruskal1(){for(int i=1;i<=n;i++){fa[i]=i;}sort(e+1,e+1+m,cmp1);for(int i=1;i<=m;i++){int u=find(e[i].u);int v=find(e[i].v);if(u!=v){fa[u]=v;if(e[i].w==1){cnt++;e[i].w=3;}}}if(cnt>k){cout<<"no solution"<<endl;exit(0);}
}
void kruskal2(){int num=0;for(int i=1;i<=n;i++){fa[i]=i;}sort(e+1,e+1+m,cmp2);for(int i=1;i<=m;i++){int u=find(e[i].u);int v=find(e[i].v);if(find(u)!=find(v)){if(e[i].w==1 && cnt==k){continue;}num++;fa[u]=v;if(e[i].w==1 && cnt<k){e[i].w=3;cnt++;}else if(e[i].w==0){e[i].w=-2;}}}if(num<n-1 || cnt<k){cout<<"no solution"<<endl;exit(0);}for(int i=1;i<=m;i++){if(e[i].w==3)cout<<e[i].u<<" "<<e[i].v<<" "<<0<<endl;if(e[i].w==-2)cout<<e[i].u<<" "<<e[i].v<<" "<<1<<endl;}
}
signed main(){cin>>n>>m>>k;for(int i=1;i<=m;i++){int u,v,w;cin>>u>>v>>w;w=!w;e[i]={u,v,w};}kruskal1();kruskal2();
}