Spanning Tree与度数的一些题

以下所有图,点数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\n");
            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\n");
        for(int i=1;i<=m;++i){
            int x=u[i],y=v[i];
            if(vis[i])printf("%d %d\n",x,y);
        }
    }
    else printf("No\n");
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--)work();
    return 0;
}

 

同理,CodeForces 1133F2的做法,也可以是把1号点的所有出边都连上,如果度数大于n/2,那么再调整即可。

(比ICPC南京 好写多了,不用考虑别的点是否度数会大于n/2)

 

posted @ 2021-01-16 12:00  *Miracle*  阅读(201)  评论(0编辑  收藏  举报