P1031 · OFFICIAL SOLUTION

P1031 雨霖铃 官方题解

Gioush OJ · P1031 雨霖铃

雨霖铃

【题意简述】

给定一张无向图,每条边类型为 0011。要求构造一棵生成树,使其中恰好有 kk 条类型为 00 的边;若不存在则输出 no solution

【Hint】

先判断类型 00 边的数量下界,再判断上界。下界来自“先用类型 11 边尽量连通后仍必须使用多少条类型 00 边”。

【提示】

若先加入所有能加入的类型 11 边,剩下为了连通而加入的类型 00 边数量就是最少需求。

【解法】

先用 Kruskal 思想求必须使用的类型 00 边数量。将类型 11 边优先加入并查集,之后为了连通而加入的类型 00 边记为 MustMust。若 Must>kMust>k,无解。

再构造答案:优先加入必要的类型 00 边,并继续补充类型 00 边直到数量达到 kk;最后用类型 11 边补齐生成树。若无法得到 n1n-1 条边,则无解。

这个过程本质上是在生成树交换空间中控制类型 00 边数量。先满足必要边,再补足可选边,可以保证不会因为较晚加入类型 11 边破坏已经达到的 kk 条限制。

【上下界与构造】

先尽量加入类型 11 边,之后为了连通而不得不加入的类型 00 边数记为 Must\operatorname{Must},它是任意生成树中类型 00 边数的下界。若 Must>k\operatorname{Must}>k,无解。

随后重新维护并查集:先加入构成下界所需的类型 00 边,再继续加入不会成环的类型 00 边,直到恰有 kk 条;最后用类型 11 边补成生成树。每一步都保持无环,且下界阶段留下了足够的连通空间,因此构造结束时得到的正是所求生成树。

【复杂度】

排序两次或按类型分组后处理,时间复杂度为 O(mlogm)\mathcal{O}(m\log m),空间复杂度为 O(n+m)\mathcal{O}(n+m)

【参考代码】

/*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;}