CF1354E Graph Coloring(构造二分图+背包)

看大神的代码一脸懵,学了很多新东西,背包理解的太浅了,二分图染色不太会。

/*
 * cf1354E
 * 题意:
 * 给出一个无向连通图,和n1,n2,n3分别表示需要染色1,2,3的节点数量。
 * 图不保证连通,并且需要保证一条边的两个节点的色号之差的绝对值为1。
 * 请你计算是否存在合理的方案,并输出其中任意一种。
 * 题解:
 * 问题可以转化为,必须给一批点染2号色,给1批点染13号色,且这两批点内部没有边,两批点之间有边。
 * 可以转化成一个二分图的构造问题。
 * 首先,我们对每个连通块构造二分图,这里用12标记二分图里的两种节点。
 * 发现奇环,说明当前连通块无法构造二分图,那么就不可能合法,直接退出。
 * 然后我们可以推导出每个连通块中1号点的数量和2号点的数量。
 * 开一个二维dp数组,表示标记i个连通块数量,j个标记为2的情况下是否存在合法
 * 所有连通块可以转化成背包,连通块里的两种节点数量就是背包的重量。
 * 可以进一步推导出状态转移方程,如果终点状态不合法直接退出。
 * 然后从终点倒推,如果当前状态不合法,说明需要取反,就反向标记。
 * 标记的时候采用这种方法:
 * 只记录当前点是2颜色还是1颜色,如果是2颜色就直接染,如果是1颜色,先看一下n1还有没有剩余,如果有染1,如果没有染2。
 */

#include<bits/stdc++.h>

using namespace std;
const int maxn=5005;
vector<int> g[maxn];
int n,m,n1,n2,n3;
int p[maxn];
int c[maxn];
int cnt;
int d1[maxn];//每个连通块中1号点的数量
int d2[maxn];//每个连通块中2号点的数量
bool dp[maxn][maxn];//标记i个连通块数量,j个标记为2的情况下是否存在合法
bool rev[maxn];//标记该连通块是否反向染色
void dfs (int u) {
    if (c[u]==1)
        d1[cnt]++;//如果当前是1号点
    else
        d2[cnt]++;//如果当前是2号点
    for (int v:g[u]) {
        if (!c[v]) {
            c[v]=3-c[u];//用1,2染色处理二分图
            p[v]=cnt;//标记v所在连通块
            dfs(v);
        }
        else if (c[u]==c[v]) {
            printf("NO\n");//如果发现染色一样,说明发现奇环,不合法
            exit(0);
        }
    }
}

int main () {
    cin>>n>>m>>n1>>n2>>n3;
    for (int i=0;i<m;i++) {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    cnt=0;
    dp[0][0]=1;//0个连通块,0个点被染色成2肯定合法
    for (int i=1;i<=n;i++) {
        if (!c[i]) {
            p[i]=cnt;//i号点所属的连通块编号是cnt
            c[i]=1;//给i号点染1颜色
            dfs(i);//处理二分图
            for (int j=d1[cnt];j<=n2;j++)
                dp[cnt+1][j]+=dp[cnt][j-d1[cnt]];//如果在已有的连通块数量下标记j-d1[cnt]数量的点为2的情况合法,那么dp[cnt+1][j]就一定合法
            for (int j=d2[cnt];j<=n2;j++)
                dp[cnt+1][j]+=dp[cnt][j-d2[cnt]];//同理
            cnt++;
        }
    }
    if (!dp[cnt][n2]) {
        //如果cnt个连通块,n2个点的情况没有合法的,输出NO。
        printf("NO\n");
        return 0;
    }
    printf("YES\n");
    while (cnt--) {
        rev[cnt]=!dp[cnt][n2-d2[cnt]];//如果只剩cnt个连通块,同时要给n2-d2[cnt]数量的点染2号颜色不存在合法的方案,说明需要取反
        if (rev[cnt])
            n2-=d1[cnt];//如果需要取反,说明当前给1号点染2号色
        else
            n2-=d2[cnt];//不需要取反,说明当前给2号点染2号色
    }
    for (int i=1;i<=n;i++) {
        if (rev[p[i]])
            c[i]=3-c[i];//如果取反,直接把染色情况置为反色(这里只有2种情况,2号色和1号色)
        if (c[i]==2)
            printf("2");
        else if (n1) {
            printf("1");//涂1号色的时候看一下n1有没有用完,如果用完了就改涂3号色
            n1--;
        }
        else
            printf("3");
    }
}

猜你喜欢

转载自www.cnblogs.com/zhanglichen/p/12913840.html
今日推荐