Codeforces 1354 E. Graph Coloring(dfs+dp)

题目链接
题目大意:
给一个图(不保证连通),需要用1,2,3对图上的每个顶点进行标号,使得标号完成之后相邻顶点的号码的差的绝对值为1,其中n1,n2,n3分别为标号为1,2,3的数量,问这种标号方法是否存在,如果存在则输出标号方案。

解题思路:
要使相邻之差绝对值为1,则相邻标号只能为1-2,2-3这两种方案,可以发现1和3的地位相同,所以可以把题目转化为对二分图染色,只要分奇偶染色就行了。
(1)对每个连通块进行dfs,检查是否为二分图,如果不是则直接输出“NO”,顺便记录每个连通块的奇数点个数O[i],和偶数点个数Z[i]。
(2)二分图染色完成,此时要完成判断是否可以使偶数点的个数之和为n2,如果满足则就是一种方案了,如果不满足则输出“NO”。因为(1)中染色虽然分了奇偶,但是他们的奇偶染色可以对换颜色,所以每个连通块可以使用Z[i]或O[i]个n2,答案就变成了01背包,询问是否会组成一个体积为n2的背包,其中每个物品的体积就是O[i]或者Z[i]。
(3)01背包完成之后,就可以根据转移路径(直接判断dp[i-1][cur-O[i]]==true即可,但一定

要把cur=cur-O[i](cur-Z[i])放在dfs之前

(重新染色会改变O[i]的数量,Z[i]也同样)重新对每个点进行染色,如果这个转移路径是从上一层经过O[i]转移过来,说明这个连通块使用了奇数个数为n2,则这个连通块的奇偶要进行对换。如果是Z[i]转移则表示还是原来的染色方案。
最后在输出奇数的时候区分1,3,输出答案即可。

解题代码:

#include<bits/stdc++.h>
using namespace std;
mt19937 rng_32(chrono::steady_clock::now().time_since_epoch().count());
typedef long long ll;
const int maxn=2e5+10;
int n1,n2,n3,n;
int m;
bool visit[maxn];
struct E{
    int to,nxt;
}e[maxn];
int tot,head[maxn];
void adde(int u,int v)
{
    e[++tot]=E{v,head[u]};
    head[u]=tot;
}
int num[maxn];
int O1[maxn],Z1[maxn],pat_head[maxn];
int part_num;
bool dfs(int u,int fa)
{
    num[u]=num[fa]^1;
    if (num[u]==1)
    O1[part_num]++;
    else
    Z1[part_num]++;
    for (int i=head[u];i;i=e[i].nxt)
    {
        if (num[e[i].to]==num[u])
        return false;
        if (num[e[i].to]==-1)
        {
            if (dfs(e[i].to,u)==false)
            return false;
        }
    }
    return true;
}
bool dp[5005][5005];
int main()
{
    cin>>n>>m;
    cin>>n1>>n2>>n3;
    int t1,t2;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&t1,&t2);
        adde(t1,t2);
        adde(t2,t1);
    }
    memset(num,-1,sizeof(num));
    num[0]=0;  
    for (int i=1;i<=n;i++)
    {
        if (num[i]==-1)
        {
            part_num++;
            pat_head[part_num]=i;
            if (!dfs(i,0))
            {
                cout<<"NO\n";
                return 0;
            }
        }
    }
    dp[0][0]=1;
    for (int i=1;i<=part_num;i++)
    {
        for (int j=min(Z1[i],O1[i]);j<=n2;j++)
        {
            if (j>=Z1[i] && dp[i-1][j-Z1[i]])
                dp[i][j]=true;
            if (j>=O1[i] && dp[i-1][j-O1[i]])
                dp[i][j]=true;
        }
    }
    if (dp[part_num][n2])
    {
        cout<<"YES\n";
        memset(num,-1,sizeof(num));
        int cur=n2;
        for (int i=part_num;i>=1;i--)
        {
            int sub1,sub2;
            if (cur>=O1[i] && dp[i-1][cur-O1[i]])
            {
                num[0]=1;
                //一定要放在dfs前面!!!
                cur-=O1[i];
                dfs(pat_head[i],0);
            }
            else
            {
                cur-=Z1[i];
                //一定要放在dfs前面!!!
                num[0]=0;
                dfs(pat_head[i],0);
            }
        }
        for (int i=1;i<=n;i++)
        {
            if (num[i])
            {
                if (n1)
                {
                    cout<<1;
                    n1--;
                }
                else
                {
                    cout<<3;
                }
            }
            else
            cout<<2;
        }
    }
    else
    {
        cout<<"NO\n";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值