• 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
    ");
        for(int i=1;i<=m;i++){
            if(vis[i]) printf("%d %d
    ",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
    ");
                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
    ");
            else print(m);
        }
        return 0;
    }
    
  • 相关阅读:
    JVM内存的划分
    劝学
    java中switch的用法
    方法传递参数的分类
    ajax缓存机制
    vuex
    keep-alive
    路由滚动行为scrollBehavior
    vue等
    防止刷新路由后参数消失
  • 原文地址:https://www.cnblogs.com/EchoZQN/p/14382314.html
Copyright © 2020-2023  润新知