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,那么说明选择当前连通块中奇数层;反之选择偶数层。

Codeforces 1354E(Graph Coloring,二分图+dp)
#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");
    }
}
View Code

 

上一篇:奇偶链表


下一篇:Eulerian Path(欧拉路径,我英语好菜~)