P5811 [IOI2019] 景点划分

题意

给定一个连通图,构造一个方案将其划分成 $A,B,C$ 三个部分,大小分别为 $a,b,c$,要求至少有两个部分为原图的联通子图。

Solution

先考虑如果是一颗树怎么做。不妨设 $a\le b\le c$,这样就只需要考虑 $a,b$,剩下的部分置成 $c$ 即可。容易发现 $a\le b\le \frac{n}{2}$,故考虑以重心为根节点,如果最大的子树大小大于等于 $a$ 且小于 $n-b$ 则直接把它弄成 $A$,然后暴力去跑一个大小为 $b$ 的集合就可以了。如果最大的子树大小小于 $a$ 的话就必然无解,直接特判掉。

对于图而言,可以先缩点,然后在 deg 上面考虑,转化上面的树上问题。然后发现实在太难写了,一大堆分讨以及乱七八糟的特殊情况。考虑把图变成 dfs 树,这样每个子树就是单独的连通块,似乎很好处理,于是很开心的写掉了。提交然后 subtask 4 和 subtask 5 一片红,一看全是合法方案误判成 No Solution 了。细细思考后发现有一种情况没有考虑,即如果重心的子树有到重心的祖先的返祖边,那么这种情况可以把该子树和祖先的那个子树合并成一个连通块,让它们都是 $A$ 类节点,而我没有考虑到这点直接判无解了,加个分类讨论就好了。

关于正确性:显然地如果有一颗子树有到重心的祖先节点有返祖边,那么直接把这个子树丢到 $A$ 集合中,令所有有 $A$ 类节点的子树大小总和为 $\Sigma size_i$,那么显然的有 $a\le \Sigma size_i \lt 2\times a$。又有 $n=a+b+c \ge 2\times a+b$,故剩下的节点数必然大于等于 $b$。

时间复杂度为 $\mathcal{O}(n)$,跑得飞快。

code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
struct edge
{
    int to,nxt;
};
int n,m,tot=1,root,son,las;
int a,b,c;
int id[]={0,1,2,3};
int ans[100010];
int head[100010],size[100010];
bool vis[100010],used[400010],flag[100010];
struct edge ed[400010];
void init(int &a,int &b,int &c)
{
    if(a>b) swap(a,b),swap(id[1],id[2]);
    if(a>c) swap(a,c),swap(id[1],id[3]);
    if(b>c) swap(b,c),swap(id[2],id[3]);
    return ;
}
void add_edge(int fr,int to)
{
    ed[++tot]=(edge){to,head[fr]};
    head[fr]=tot;
    return ;
}
void dfs(int fr,int fa,int &num,int opt,bool type)
{
    if(num==0)
        return ;
    ans[fr]=opt;
    num--;
    for(int i=head[fr];i!=0;i=ed[i].nxt)
    {
        int to=ed[i].to;
        if(to==fa||ans[to]!=0)
            continue;
        if(type==false&&used[i]!=true)
            continue;
        if(type==true&&to==root)
            continue;
        dfs(to,fr,num,opt,type);
    }
    return ;
}
void build(int fr,int fa)
{
    size[fr]=1;
    vis[fr]=true;
    int tmp=0;
    for(int i=head[fr];i!=0;i=ed[i].nxt)
    {
        int to=ed[i].to;
        if(vis[to]==true)
            continue;
        used[i]=used[i^1]=true;
        build(to,fr);
        size[fr]+=size[to];
        if(size[tmp]<size[to])
            tmp=to;
    }
    if(std::max(size[tmp],n-size[fr])<=n/2)
    {
        root=fr,las=fa;
        son=tmp;
    }
    return ;
}
int main(int argc,const char *argv[])
{
    n=read(),m=read();
    a=read(),b=read(),c=read();
    init(a,b,c);
    for(int i=1;i<=m;i++)
    {
        int fr=read()+1,to=read()+1;
        add_edge(fr,to);
        add_edge(to,fr);
    }
    build(1,0);
    if(std::max(size[son],n-size[root])<a)
    {
        dfs(las,root,a,id[1],true);
        dfs(root,0,b,id[2],false);
    }
    else
    {
        if(size[son]<n-size[root])
            son=las;
        dfs(son,root,a,id[1],false);
        dfs(root,0,b,id[2],false);
    }
    for(int i=1;i<=n;i++)
    {
        if(a==0&&ans[i]==0)
            ans[i]=id[3];
        else if(a!=0)
            ans[i]=0;
    }
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    return 0;
}
posted @ 2023-11-05 15:37  Che_001  阅读(25)  评论(0)    收藏  举报  来源