CF-1354 E. Graph Coloring(二分图,背包,背包方案输出)

E. Graph Coloring

链接

n个点m条边的无向图,不保证联通,给每个点标号1,2,3。1号点个数n1,2号点个数n2,3号点个数n3。且每条边的两点,标号之差绝对值为1。如果有合法方案,需输出方案。

考虑每个联通子图,2只可以和1或者3连边,1只能和2连边,3只能和2连边,那么将1,3归为一堆,2归为一堆。每一堆内不存在边,构成一个独立点集,那么很明显是一个二分图,每次DFS可以找到二分图两部点的个数,如果存在奇环那么直接输出NO

对于每个联通子图,一个二分图,假设左部有 x 个点,右部有y个点,那么可以给x个点标2号,或者给 y 个点标2号。问最后能否刚好凑够 n2 个2号点。这显然是一个背包问题。

每个联通子图是一个物品,二分图两部分点的数量就是体积,可以记录路径也可以不记录。因为目标是凑够 n2 个点,那么如果第 i 个物品选择的不是二分图中标记为 2 的点,那么认为这个物品是反选的,也是将标记为 1 的点最终标记成了 2。

对于1号点和3号点,在整个过程中都被标记成了1,所以只需要输出所有标记为1的点即可,如果n1个 1 全部输出,那么再紧接着输出 3即可。

需要标记的东西:第 i 个物品的两个体积(左部点个数和右部点个数), 每个点的标号,每个点所属的物品编号,物品是否要反选(最后DP结束后倒推即可)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 5000 + 5;
const int M = 200010;
int head[N], ver[M], nxt[M], tot, cnt;
int n, m, n1, n2, n3;
int c[N], be[N], c1[N], c2[N], rev[N];
int d[N][N];
void add(int x, int y){
    ver[++tot] = y, nxt[tot] = head[x], head[x] = tot;
}
bool dfs(int x, int col){
    c[x] = col;
    be[x] = cnt;
    if(c[x] == 1) c1[cnt] ++;
    else c2[cnt] ++;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(!c[y]){
            if(!dfs(y, 3 - col)) return false;
        }
        if(c[y] + c[x] != 3) return false;
    }
    return true;
}
int main(){
    scanf("%d%d", &n,&m);
    scanf("%d%d%d", &n1, &n2, &n3);
    for(int i=1;i<=m;i++){
        int x, y;scanf("%d%d", &x, &y);
        add(x, y);add(y, x);
    }
    d[0][0] = 1;
    for(int i=1;i<=n;i++){
        if(c[i]) continue;
        cnt++;
        if(!dfs(i, 1)){
            puts("NO");
            return 0;
        }
        for(int j=c1[cnt];j<=n2;j++){
            d[cnt][j] |= d[cnt-1][j-c1[cnt]];
        }
        for(int j=c2[cnt];j<=n2;j++){
            d[cnt][j] |= d[cnt-1][j-c2[cnt]];
        }
    }
    if(!d[cnt][n2]) {
        puts("NO");return 0;
    }
    puts("YES");
    while(cnt){
        rev[cnt] = d[cnt-1][n2-c1[cnt]];
        if(rev[cnt]) n2 -= c1[cnt];
        else n2 -= c2[cnt];
        cnt --;
    }
    for(int i=1;i<=n;i++){
        if(rev[be[i]]) c[i] = 3 - c[i];
        if(c[i] == 2) putchar('2');
        else if(n1 > 0) putchar('1'), n1--;
        else putchar('3'); 
    }

    return 0;
}
posted @ 2020-05-17 23:13  kpole  阅读(381)  评论(0编辑  收藏  举报