Codeforces 1354E 题解

题面

现有一个 $n$ 个点 $m$ 条边的无向图,要求用 ${1,2,3}$ 对每个点染色,使得相邻的两个点的权值的绝对值之差刚好为 $1$。现在需要求出任意一个方案,使得一共染了 $n_1$ 个 $1$,$n_2$ 个 $2$,$n_3$ 个 $3$。保证 $n_1+n_2+n_3=n$。

无解输出 NO

有解输出一行 YES,并在下一行输出一个字符串,第 $i$ 位表示点 $i$ 的颜色。

$1 \leq n \leq 5 \times 10^3$, $0 \leq m \leq 10^5$

题解

首先,如果有奇环显然无解。

多手推几次可以发现,$1,3$ 可以互换位置,$2$ 必定间隔出现。

所以可以进行二分图染色。

每个联通块中,必定有黑色全为 $2$,白色全为 $1,3$ ;或黑色全为 $1,3$,白色全为 $2$。

因此可以跑个背包来判断是否能恰好组合出 $n_2$,并逆推出方案,然后将这些点染成 $2$,其他点随意染成 $1,3$ 即可。

实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
struct edge{
    int to,nxt;
}ed[101010*2];int edcnt=0;
int hd[5050];
inline void adde(int u,int v){
    ed[++edcnt].to=v;
    ed[edcnt].nxt=hd[u];
    hd[u]=edcnt;
}
/*
blk[i] - i号点所在联通块
col[i] - i号点黑白染色的颜色
ans[i] - i号点答案中的颜色
*/
int ni[4],blk[5050],vis[5050],col[5050],ans[5050];
vector<int> blkin[5050];
int cnt[5050][2],dp[5050][5050];
bool dfs(int blkid,int u,int fa){
    if(vis[u]){
        if(col[u]!=!col[fa]) return true;
        return false;
    }
    blk[u]=blkid;
    blkin[blkid].push_back(u);
    vis[u]=1;
    col[u]=!col[fa];
    cnt[blkid][col[u]]++;
    for(int id=hd[u];id;id=ed[id].nxt){
        int v=ed[id].to;
        if(dfs(blkid,v,u)) return true;
    }
    return false;
}
int totblk=0;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=3;++i)scanf("%d",&ni[i]);
    for(int i=1,u,v;i<=m;++i){
        scanf("%d%d",&u,&v);
        adde(u,v);
        adde(v,u);
    }
    for(int i=1;i<=n;++i){
        if(blk[i])continue;
        if(dfs(++totblk,i,i)){
            puts("NO");
            return 0;
        }
    }
    
    //背包 似乎可以用翻转的思路来降到一维
    dp[0][0]=1;
    for(int i=1;i<=totblk;++i){
        for(int j=n;j>=0;--j){
            if(j-cnt[i][0]>=0 && dp[i-1][j-cnt[i][0]])
                dp[i][j]=1;
            if(j-cnt[i][1]>=0 && dp[i-1][j-cnt[i][1]])
                dp[i][j]=1;
        }
    }
    int left=ni[2];
    if(!dp[totblk][left]){
        puts("NO");
        return 0;
    }

    //逆推方案并染色
    for(int i=totblk;i>=1;--i){
        if(cnt[i][0] <= left && dp[i-1][left-cnt[i][0]]){
            for(int v:blkin[i])
                if(col[v]==0)ans[v]=2,left--;
        }else{
            for(int v:blkin[i])
                if(col[v]==1)ans[v]=2,left--;
        }
    }
    for(int i=1;i<=n;++i){
        if(ans[i]) continue;
        if(ni[1]){
            ans[i]=1;
            ni[1]--;
        }else ans[i]=3;
    }
    puts("YES");
    for(int i=1;i<=n;++i)printf("%d",ans[i]);
}