Codeforces1354 E. Graph Coloring(01染色+dp)

题意:

给n个点m条边的无向图,和三个整数n1,n2,n3,n1+n2+n3=n
现在要你给每个点赋予点权1或2或3,满足下面条件:

  • 1的个数恰好为n1,2的个数恰好为n2,3的个数恰好为n3
  • 相邻点的差值必须为1

如果无解输出NO,否则输出YES和赋值方案

数据范围:n<=5e3,m<=1e5

解法:

相邻点的差值必须为1,那么1只能和2匹配,3只能和2匹配,2只能和1,3匹配
将21,3分成两部分,那么每部分内部不存在边,因此是一个二分图
对原图进行二分图染色,将原图中的多个连通块分成若干二元组(a,b)
二元组每部分对应2或者1,3,需要自己划分
问题就变为是否存在一种划分,满足一部分的和为n2,另一部分的和为n3
可以用dp解决,但是要输出方案,看了高rank选手的代码发现记录方法挺巧妙的

code:

#include
using namespace std;
const int N=5e3+5;
vector<int>g[N];
int col[N];
int n1,n2,n3;
int n,m;
//
vector<int>temp[N][2];
int err;
int cnt;
int d[N][N];
int ans[N];
void dfs(int x,int c){
    if(err)return ;
    col[x]=c;
    temp[cnt][c==1].push_back(x);
    for(int v:g[x]){
        if(!col[v]){
            dfs(v,-c);
        }else if(col[v]==col[x]){
            err=1;
        }
    }
}
signed main(){
    scanf("%d%d",&n,&m);
    scanf("%d%d%d",&n1,&n2,&n3);
    for(int i=1;i<=m;i++){
        int a,b;scanf("%d%d",&a,&b);
        g[a].push_back(b);
        g[b].push_back(a);
    }
    for(int i=1;i<=n;i++){
        if(!col[i]){
            cnt++;
            dfs(i,1);
            if(err){
                puts("NO");return 0;
            }
        }
    }
    memset(d,-1,sizeof d);
    d[0][0]=1;
    for(int i=1;i<=cnt;i++){
        int x=temp[i][0].size();
        int y=temp[i][1].size();
        for(int j=0;j<=n;j++){
            if(d[i-1][j]!=-1){
                d[i][j+x]=j;//直接记录前驱
                d[i][j+y]=j;
            }
        }
    }
    if(d[cnt][n2]==-1){
        puts("NO");return 0;
    }
    puts("YES");
    int now=n2;
    for(int i=cnt;i>=1;i--){
        int cc=now-d[i][now];//数量
        now=d[i][now];
        int cur=0;
        if((int)temp[i][1].size()==cc)cur=1;
        for(int v:temp[i][cur]){
            ans[v]=2;
        }
        for(int v:temp[i][cur^1]){
            if(n1)ans[v]=1,n1--;
            else ans[v]=3;
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d",ans[i]);
    }
    return 0;
}

你可能感兴趣的:(Codeforces1354 E. Graph Coloring(01染色+dp))