Degree of Spanning Tree 南京ICPC2020 生成树+并查集+思维

Degree of Spanning Tree 生成树 + 思维

题目大意:

给你一张 \(n\) 个节点 \(m\) 条边的无向图,你可以删去一些节点使得这张图变成一棵树,要求每一个节点的度数小于等于 \(\frac{n}{2}\)

题解:

要一步一步的分析

  • 首先分析如何把一张图变成一棵树,并查集即可。

  • 对于一棵树,它最多只有一个节点的度数要 \(>\frac{n}{2}\) ,定义 \(d(i)\) 表示节点 \(i\) 的度数,对于任意两个节点 \((u,v)\)\(d(u)+d(v)<=n\) ,这个很容易理解,你可以假设 \(u\) 和它所连的点形成一个连通块,那么 \(v\) 和它所连的点中最多有一个点连到了 \(u\) 所在的哪个连通块,如果有两个及以上就会形成一个环,假设 \(u\) 所在的连通块的数量是 \(x\),那么减去 \(u\) ,所以度数是 \(x-1\)\(v\) 所在的连通块数量最大是 \(n-x\),所以度数是 \(n-x-1\) ,如果 \(u\)\(v\) 直接相连,那么会加上2的度数,所以就是 \(n\)

    因为 \(d(u)+d(v)<=n\) 所以最多有一个节点的度数 \(>\frac{n}{2}\)

  • 所以先变成任意的一个生成树,找到度数 \(>\frac{n}{2}\) 的这个节点 \(rt\),接下来遍历不在生成树里面的边,如果存在一条边,它的加入会形成一个包含 \(rt\) 的环,那么就加入这条边,删去一条和 \(rt\) 相连的边。

  • 注意在这个过程中,可能会出现另外一个点 \(id\) ,使得 \(d[id]>\frac{n}{2}\) ,但是从上述证明中可以发现的是的是 \(id\) 一定和 \(rt\) 一定相邻,否则 \(d[id]+d[rt]<=n-2\) ,所以在加边的过程中注意一下不要出现这种情况即可。

难点:

  • 每次加一条边,如何保证这条边的加入会形成一个包含 \(rt\) 的环。
    • 因为我们只要研究一个根节点,而且这个根节点是已经确定下来的。
    • 那么我们找到这个根节点的所有儿子节点 \(v\),以这些节点作为根节点来遍历他们的子节点,然后更新他们的子节点的父节点为 \(v\) 即可。
    • 之后的加边,我只要判断两个节点是不是属于不同的子节点,如果是,那么不让他称为一个新的 \(id\) 即可,如果不是,那么就要忽略这条边的处理

这个题目写的时候,还是要注意细节,最后如果一条边被删去了,那么要对这个边的并查集数组重新赋值,这个赋值是有方向的!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
struct node{
    int u,v,id;
    node(int u=0,int v=0,int id=0):u(u),v(v),id(id){}
}e[maxn];
vector<node>G[maxn];
bool vis[maxn];
int f[maxn],in[maxn],fa[maxn],dep[maxn];
void add(int u,int v,int id){
    G[u].push_back(node(u,v,id));
    G[v].push_back(node(v,u,id));
}
int findx(int x){
    return f[x]==x?x:f[x] = findx(f[x]);
}
void unite(int x,int y){
    x = findx(x),y = findx(y);
    if(x==y) return ;
    f[x] = y;
}
bool same(int x,int y){
    return findx(x)==findx(y);
}
void dfs(int u,int pre,int t){
    f[u] = t,dep[u] = dep[pre]+1;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i].v;
        if(v==pre) continue;
        dfs(v,u,t);
    }
}
void print(int m){
    printf("Yes\n");
    for(int i=1;i<=m;i++){
        if(vis[i]) printf("%d %d\n",e[i].u,e[i].v);
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        int n,m,rt = 0;
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++) f[i] = i,G[i].clear(),in[i] = 0;
        for(int i=0;i<=m;i++) vis[i] = false;
        for(int i=1;i<=m;i++) {
            int u,v;
            scanf("%d%d",&u,&v);
            e[i] = node(u,v,i);
            if(!same(u,v)) unite(u,v),vis[i] = true,in[u]++,in[v]++,add(u,v,i);
            if(in[u]>in[rt]) rt = u;
            if(in[v]>in[rt]) rt = v;
        }
        if(n==3){
            printf("No\n");
            continue;
        }
        dep[rt] = 0,f[rt] = rt;
        for(int i=0;i<G[rt].size();i++){
            int v = G[rt][i].v;
            fa[v] = G[rt][i].id;
            dfs(v,rt,v);
        }
        for(int i=1;i<=m;i++){
            if(in[rt]<=n/2) break;
            int u = e[i].u,v = e[i].v;
            int fu = findx(u),fv = findx(v);
            if(fu==fv||u==rt||v==rt) continue;
            if(dep[u]<dep[v]) swap(u,v),swap(fu,fv);
            if(dep[v]==1&&in[u]>in[v]) swap(u,v),swap(fu,fv);
            ++in[u],++in[v],--in[rt],--in[fv];
            vis[e[i].id] = true,vis[fa[fv]] = false;
            f[fv] = fu;//!!!!
        }
        if(in[rt]>n/2) printf("No\n");
        else print(m);
    }
    return 0;
}
posted @ 2021-02-06 18:02  EchoZQN  阅读(164)  评论(2编辑  收藏  举报