以下所有图,点数n是1e5,边数是1e6
CodeForces 1133F1 Spanning Tree with Maximum Degree
题意:
找到一个生成树,使得度数最大的点度数最大
Sol:
直接找原图中度数最大的点p,把p的所有边都加入生成树,再kruskal即可
CodeForces 1133F2 Spanning Tree with One Fixed Degree
题意:
找到一个生成树,使得1号点度数恰好为K
Sol:
法一:
把一号点删掉,看图被分成了多少个连通块(dfs即可),每个连通块涂成一种颜色
把一号点和每个连通块至少连一个边。和K比较决定即可。
或者求割点,如果一号点是割点,也可以求出周围的块(麻烦,注意割点不完全属于任何点双)
法二:
下面写。
(ICPC)亚洲区域赛(南京) D.Degree of Spanning Tree
题意:
找到一个生成树,使得每个点度数小于n/2
Sol:
前面的方法不行了。
法一:(?)
求割点,考虑每个割点pi对周围的每个点双至少要连接一条边。
但是在pi的出边连接的点也是割点pj的时候,无法判断pj“属于”哪个点双。。(反正不会优化。。。。只能n^2)
法二:
反正来。玩修正主义
先随便找一个生成树。
如果有一个点度数>n/2(这样的点最多一个,反证即可),这个点就当作根root。
考虑其他没有加入的边,如果和root构成环,那么就砍掉root和某个儿子的边,并加入新边。
注意,加入新边不能使得有新的点度数>n/2,加入的时候判断即可。
这样是对的。
因为最后正确答案如果存在,那么会有多个。
在有答案的情况下,(即每个割点连接的点双不多于n/2个)
随机生成树度数大于n/2的点root,
如果是原图的割点,那么可以调整
如果不是原图的割点,那么也可以调整。
实现方法:(比较巧妙)
(首先可以LCT暴力维护LCA)
然后其实可以把root的每个儿子子树看成一块,用并查集,开始的时候每个块的点,分别指向对应的root的儿子。
然后加入边的时候,涉及si,sj两个儿子,
儿子si和root的边断了,就让si并查集中的father为sj,对应连通块的合并。
(因为si和root的边不会再连上,所以这样就是对的。)
代码:
(来源:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=46411249)
#include<bits/stdc++.h> using namespace std; const int M=5e5+9; int n,m,num,root; int f[M],u[M],v[M],du[M]; int head[M],in[M]; bool vis[M]; struct P{int to,ne,id;}e[M<<2]; void dfs(int u,int fa,int top){ f[u]=top; for(int i=head[u];i;i=e[i].ne){ int v=e[i].to; if(v!=fa)dfs(v,u,top); } } int find(int x){ return f[x]==x?x:f[x]=find(f[x]); } void work(){ num=0;root=0; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)f[i]=i,head[i]=0,du[i]=0,in[i]=0; for(int i=1;i<=m;++i)vis[i]=0; for(int i=1;i<=m;++i){ scanf("%d%d",&u[i],&v[i]); int x=u[i],y=v[i]; int fx=find(x),fy=find(y); if(fx==fy)continue; f[fy]=fx; e[++num]=P{y,head[x],i};head[x]=num; e[++num]=P{x,head[y],i};head[y]=num; du[x]++;du[y]++; vis[i]=1; } for(int i=1;i<=n;++i){ if(find(i)!=find(1)){ printf("No "); return; } if(du[i]>n/2)root=i; } for(int i=1;i<=n;++i)f[i]=i; for(int i=head[root];i;i=e[i].ne){ in[e[i].to]=e[i].id; dfs(e[i].to,root,e[i].to); } for(int i=1;i<=m;++i){ int x=u[i],y=v[i]; int fx=find(x),fy=find(y); if(fx==fy)continue; if(x==root||y==root)continue; if(!in[fy]&&!in[fx])continue; du[x]++,du[y]++; du[root]--;du[fy]--; if(du[x]>n/2||du[y]>n/2){ du[fy]++; du[fx]--; if(du[x]>n/2||du[y]>n/2){ du[root]++; du[fx]++; du[x]--;du[y]--; continue; } vis[in[fx]]=0; vis[i]=1; f[fx]=fy; continue; } vis[in[fy]]=0; vis[i]=1; f[fy]=fx; } if(du[root]<=n/2){ printf("Yes "); for(int i=1;i<=m;++i){ int x=u[i],y=v[i]; if(vis[i])printf("%d %d ",x,y); } } else printf("No "); } int main(){ int T; scanf("%d",&T); while(T--)work(); return 0; }
同理,CodeForces 1133F2的做法,也可以是把1号点的所有出边都连上,如果度数大于n/2,那么再调整即可。
(比ICPC南京 好写多了,不用考虑别的点是否度数会大于n/2)