看大神的代码一脸懵,学了很多新东西,背包理解的太浅了,二分图染色不太会。
题意:
给你一个不保证连通的无向图,请你给图上的所有节点染色,有三种颜色1,2,3,并且事先给出三种颜色的点数n1,n2,n3,并且需要请你保证每条边相连的两个节点的颜色编号之差的绝对值等于1,请你判断是否存在合法方案,并输出任意一种合法方案。
题解:
首先是问题模型的转化,先考虑只有一个连通块,从题目条件很容易想到,我们需要给一批节点染2号色,给另一批节点染13号色,且这两批点内部不能有边。
那么问题可以进一步简化为,给一批节点染2号色,给另一批节点染1号色,把题目中13号颜色统一看成1号色。
这样就转化成一个二分图问题了,我们只需要对这个连通块做一个二分图构造,如果发现奇环直接不合法,然后枚举两个部分的节点,分别用2号色尝试染,看看是否符合题给条件。
处理完每个连通块,再考虑多个连通块。我们对每个连通块做二分图的构造,这样每个连通块都能分为两个独立点集。
现在问题进一步转化为,是否存在一个合法的对每个连通块做染色的方案,使得最后的总方案满足题给条件。
这一步考虑背包。把每个连通块的两批节点数视作物品的体积,开一个二维dp数组,表示在标记i个连通块的数量,j个点染2号颜色的情况下是否合法。
然后依次枚举每个连通块,可以写出状态转移方程:
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]];//同理
先判断dp终点,如果不合法就退出。
然后,从终点开始往前倒推,如果当前对应的dp数组显示不合法,说明要对当前连通块做取反操作,即对2染色1,对1染色2。
最后输出合法染色方案的时候,染色为2的节点正常输出,染色为1的节点先输出1,n1用完了就输出3,问题得到解决。
/* * 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 ");//如果发现染色一样,说明发现奇环,不合法 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 "); return 0; } printf("YES "); 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"); } }