Codeforces 1354E(Graph Coloring,二分图+dp)
题意:给你n1个1,n2个2,n3个3以及n和顶点(保证n=n1+n2+n3),m条双向边。然后构成一个图,此图可能为非连通图,对该图进行染色(可以染1或2或3三种颜色),每相邻两个点颜色大小绝对值之差为1,问能否染成功?如果成功,输出每个点的染色方案。
难度系数:图论2100
解法:在比赛的时候直接去看的这个题,没写出来。那时候想到了以下几点:把1和3看成1(因为1和3本身不可能相连,所以看成一体)这是一个二分图,保证二分图条件后进行染色,然后因为有多个连通块构成,这时候我们把每一层的图转化成树,然后统计每一个连通块位于单数层的总结点数量cnt1,和双数层总结点数量cnt2,。然后dfs构造多个连通块再回溯结果,这么做最后TLE了。
不过想法还是对了一半,这个dfs用背包dp代替就不会出现那种情况。用dfs去对每一个连通块建树,并标记深度,在图中如果有环的边数为奇数,显然就不是二分图。可以直接输出NO返回结果。
开一个二维dp数组,dp[i][j],i表示是第几个连通块,j表示当前使用过的节点数量。dp为PII型,方便日后回溯搭配点。first用来记录转移之前一共拥有的数量,second表示当前选取了该连通块的奇数层还是偶数层,0表示奇数层,1表示偶数层
开一个两层的vector<vector<int>>odd,even.odd用来记录每一个连通块所有奇数层一共有的结点数量。even用来记录偶数节点数量。
因为有可能会出现n2等于0的情况或者n1+n3=0的情况,所以初始化dp的时候全部初始化成-1(memset(dp,-1,sizeof(dp));
接下来分析状态转移方程。考虑dp[0][odd[0].size()]为一开始的状态,他里面所含的节点大小j为odd[0].size(),显然他是通过j=0来转化得到的但由于是第一个联通块,所以在他之前没有别的连通块,但是因为该点确实存在,所以不能把first定为-1,应该定为0。接下来就是dp的状态转移了。
第一层for表示第几个连通块,第二次for表示已包含了0~n个结点。dp转移的时候,从当前转移到下一个,判断条件是当前dp[i][j].first是否>=0,并且加上第i+1层的奇数层次数目或者偶数层次数目仍然<=n,如果可以那就能转移
第二步判断:如果dp[总组数][n2].first>=0,那么说明可以转化到n2。所以只需要通过回溯不断寻找每个节点的答案就行。每一次更新now=他的后继转移过来的,即他的first。组数t每次减少1,直到为0终止转移。判断如果now.second==0,那么说明选择当前连通块中奇数层;反之选择偶数层。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,n) for(int i=a;i<=n;i++) #define per(i,n,a) for(int i=n;i>=a;i--) #define endl '\n' #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) #define IO ios::sync_with_stdio(false);cin.tie(0); using namespace std; const int INF=0x3f3f3f3f; const ll inf=0x3f3f3f3f3f3f3f3f; const int mod=1e9+7; const int maxn=1e5+5; int tot,head[maxn]; struct E{ int to,next; }edge[maxn<<1]; void add(int u,int v){ edge[tot].to=v; edge[tot].next=head[u]; head[u]=tot++; } vector<int> vec; int a[5],n,m,dfn[5005],tott=0,depth[5005]; void dfs(int x,int fa){ vec.push_back(x); dfn[x]=++tott; depth[x]=depth[fa]+1; for(int i=head[x];i!=-1;i=edge[i].next){ int v=edge[i].to; if(v==fa) continue; if(!dfn[v]) dfs(v,x); else{ if((dfn[x]-dfn[v]+1)%2==1){ puts("NO");exit(0); } } } } pair<int,int> dp[5005][5005]; int ans[5005]; int main(){ scanf("%d%d",&n,&m);mem(head,-1); rep(i,0,2) cin>>a[i]; while(m--){ int u,v;scanf("%d%d",&u,&v); add(u,v);add(v,u); } vector<vector<int> >odd,even; int groupnum=0; for(int i=1;i<=n;i++){ if(!dfn[i]){ vec.clear(); dfs(i,0); vector<int> v1,v2; for(auto it:vec){ if(depth[it]%2==1) v1.push_back(it); else v2.push_back(it); } ++groupnum; odd.push_back(v1); even.push_back(v2); } } int blocks=groupnum; mem(dp,-1); dp[0][odd[0].size()]={0,0}; dp[0][even[0].size()]={0,1}; for(int i=0;i<blocks-1;i++){ for(int j=0;j<=n;j++){ if(dp[i][j].first>=0){ if(j+odd[i+1].size()<=n) dp[i+1][j+odd[i+1].size()]={j,0}; if(j+even[i+1].size()<=n) dp[i+1][j+even[i+1].size()]={j,1}; } } } if(dp[blocks-1][a[1]].first>=0){ puts("YES"); pair<int,int> now=dp[blocks-1][a[1]]; int t=blocks-1; while(t>=0){ if(now.second==0){ for(auto it:odd[t]){ ans[it]=2; } for(auto it:even[t]){ if(a[0]){ans[it]=1;--a[0];} else{ans[it]=3;} } } else{ for(auto it:even[t]){ ans[it]=2; } for(auto it:odd[t]){ if(a[0]){ans[it]=1;--a[0];} else{ans[it]=3;} } } if(t==0) break; --t; now=dp[t][now.first]; } rep(i,1,n) cout<<ans[i]; }else{ puts("NO"); } }