P1031 雨霖铃 官方题解
Gioush OJ · P1031 雨霖铃
雨霖铃
【题意简述】
给定一张无向图,每条边类型为 或 。要求构造一棵生成树,使其中恰好有 条类型为 的边;若不存在则输出 no solution。
【Hint】
先判断类型 边的数量下界,再判断上界。下界来自“先用类型 边尽量连通后仍必须使用多少条类型 边”。
【提示】
若先加入所有能加入的类型 边,剩下为了连通而加入的类型 边数量就是最少需求。
【解法】
先用 Kruskal 思想求必须使用的类型 边数量。将类型 边优先加入并查集,之后为了连通而加入的类型 边记为 。若 ,无解。
再构造答案:优先加入必要的类型 边,并继续补充类型 边直到数量达到 ;最后用类型 边补齐生成树。若无法得到 条边,则无解。
这个过程本质上是在生成树交换空间中控制类型 边数量。先满足必要边,再补足可选边,可以保证不会因为较晚加入类型 边破坏已经达到的 条限制。
【上下界与构造】
先尽量加入类型 边,之后为了连通而不得不加入的类型 边数记为 ,它是任意生成树中类型 边数的下界。若 ,无解。
随后重新维护并查集:先加入构成下界所需的类型 边,再继续加入不会成环的类型 边,直到恰有 条;最后用类型 边补成生成树。每一步都保持无环,且下界阶段留下了足够的连通空间,因此构造结束时得到的正是所求生成树。
【复杂度】
排序两次或按类型分组后处理,时间复杂度为 ,空间复杂度为 。
【参考代码】
/*Author:EhundateghDate:2026/7/23Name:rainbell.cppYou steal,I kill.*/#include <vector>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 20010#define MAXM 100010using namespace std;int Fa[MAXN],Must,n,m,Can=0,k;struct edge{ int St,Ed; int Type;}Edge[MAXM];vector <edge> Ans;bool cmpa(edge a,edge b){return a.Type<b.Type;}bool cmpb(edge a,edge b){return a.Type>b.Type;}int Find(int x){return Fa[x]==x?Fa[x]:Fa[x]=Find(Fa[x]);}void Merge(int x,int y){Fa[Find(x)]=Find(y);}bool Get_Tog(){ int Tag; for(int i=1;i<=n;i++) Fa[i]=i; for(int i=1;i<=m;i++){ if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue; Merge(Edge[i].St,Edge[i].Ed); } Tag=Find(1); for(int i=1;i<=n;i++){if(Tag!=Find(i)) return false; } return true;}void Kruskal1(){ sort(Edge+1,Edge+m+1,cmpb); for(int i=1;i<=n;i++) Fa[i]=i; int Count=0,i; for(i=1;i<=m;i++){ if(Edge[i].Type==0) break; if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue; Count++; Merge(Edge[i].St,Edge[i].Ed); } for(i;i<=m;i++){ if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue; Merge(Edge[i].St,Edge[i].Ed); Ans.push_back(Edge[i]); } Must=n-1-Count;}bool Kruskal2(){ sort(Edge+1,Edge+m+1,cmpa); for(int i=1;i<=n;i++) Fa[i]=i; k-=Must; for(int i=0;i<(int)Ans.size();i++){ Merge(Ans[i].Ed,Ans[i].St); } for(int i=1;i<=m;i++){ if(Edge[i].Type==0&&(!k)) continue; if(Edge[i].Type==1&&(k)) return false; if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue; Merge(Edge[i].St,Edge[i].Ed); Ans.push_back(Edge[i]); if(Edge[i].Type==0) k--; } return (int)Ans.size()==n-1;}void Solve(){ int In1,In2,In3; Ans.clear();Can=0;Must=0; scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++){ scanf("%d%d%d",&In1,&In2,&In3); Edge[i]={In1,In2,In3}; if(In3==0) Can++; } if(!Get_Tog()){ puts("no solution"); return; } Kruskal1(); if(Must>k) {puts("no solution");return;} if(!Kruskal2()){puts("no solution");return;} for(int i=0;i<(int)Ans.size();i++){ printf("%d %d %d\n",Ans[i].St,Ans[i].Ed,Ans[i].Type); } return;}int main(){ int c,T; freopen("rainbell.in","r",stdin); freopen("rainbell.out","w",stdout); scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}