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