E - Graph Coloring(奇环+dp)
题:https://codeforces.com/contest/1354/problem/E
题意:给定无向图(无自环和重边,可能不连通),每个节点可赋值为“1,2,3”的值,问能否给节点赋值,使得这个图满足有n1个1节点,n2个2节点,n3个3节点,且相邻节点差的绝对值要=1
分析:由题可得,2临点可以是1或3,而1和3只能是2。那么我们可以简单地把2看成第一类点,把1和3看成第二类点,那么我们只需要对图进行二分图染色就可以把俩类点分开或者判断其是否存在;
由于图可能不连通,可能会使问题dp化,因为要决定第 i 个图的黑点是第一类点还是第二类点。我们假定有若干个子图;
若各个子图不存在奇环则证明可以染色成功,因为奇环必定会导致一定存在有同一类点相连(我们的目的是使不同类点相连);
在染色成功的基础上我们可以认定每个子图就是一颗树,因为假定子树 i 有个偶数环,那么我可以把其中一条边忽略掉而不对答案造成影响同时把图简化成树;
接着我们考虑分配也就是dp过程,其实我们只要判断各个子图取出来的某类点之和是否能恰好等于n2即可。dp[i][j]表示前 i 个子图能否达到 j 个点。而要输出方案,我们用pair要记录路径,first代表 j 的来源,而second(0,1)表示是第一类点还是第二类点;
最后判断dp[子图数目][n2]状态是否存在,再输出方案,对于第二类点的1和3只要贪心地分配即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define pb push_back #define pii pair<int,int> const int M=5e3+5; int n,m; vector<int>g[M],tmp,tmp1,tmp2; vector<vector<int> >odd,even; pii dp[M][M]; int deep[M],ans[M]; void dfs(int u,int fa){ tmp.pb(u); deep[u]=deep[fa]+1; for(auto v:g[u]){ if(v!=fa){ if(deep[v]){ if((deep[u]-deep[v]+1)&1){///判断是否存在奇数环 puts("NO"); exit(0); } } else dfs(v,u); } } } int main(){ scanf("%d%d",&n,&m); int n1,n2,n3; scanf("%d%d%d",&n1,&n2,&n3); for(int u,v,i=1;i<=m;i++){ scanf("%d%d",&u,&v); g[u].pb(v),g[v].pb(u); } for(int i=1;i<=n;i++) if(!deep[i]){ tmp.clear(),tmp1.clear(),tmp2.clear(); dfs(i,0); for(auto v:tmp) if(deep[v]&1) tmp1.pb(v); else tmp2.pb(v); odd.pb(tmp1); even.pb(tmp2); } memset(dp,-1,sizeof(dp)); dp[0][(int)odd[0].size()]=make_pair(0,0); dp[0][(int)even[0].size()]=make_pair(0,1); int tot=(int)odd.size(); for(int i=0;i<tot-1;i++) for(int j=0;j<=n2;j++) if(dp[i][j].first>=0){ if(j+(int)odd[i+1].size()<=n2) dp[i+1][j+(int)odd[i+1].size()]=make_pair(j,0); if(j+(int)even[i+1].size()<=n2) dp[i+1][j+(int)even[i+1].size()]=make_pair(j,1); } if(dp[tot-1][n2].first>=0){ puts("YES"); tot--; pii now=dp[tot][n2]; while(true){ if(now.second==0){ for(auto v:odd[tot]) ans[v]=2; for(auto v:even[tot]){ if(n1>0) ans[v]=1,n1--; else ans[v]=3; } } else{ for(auto v:even[tot]) ans[v]=2; for(auto v:odd[tot]){ if(n1>0) ans[v]=1,n1--; else ans[v]=3; } } tot--; if(tot<0) break; now=dp[tot][now.first]; } for(int i=1;i<=n;i++) printf("%d",ans[i]); } else{ if(n==1100&&m==100000) cout<<"!!"<<endl; puts("NO"); } return 0; }