Educational Codeforces Round 87 (Rated for Div. 2) E. Graph Coloring (DFS+DP+状态记录+构造)

You are given an undirected graph without self-loops or multiple edges which consists of nn vertices and mm edges. Also you are given three integers n1n1 , n2n2 and n3n3 .

Can you label each vertex with one of three numbers 1, 2 or 3 in such way, that:

  1. Each vertex should be labeled by exactly one number 1, 2 or 3;
  2. The total number of vertices with label 1 should be equal to n1n1 ;
  3. The total number of vertices with label 2 should be equal to n2n2 ;
  4. The total number of vertices with label 3 should be equal to n3n3 ;
  5. |colucolv|=1|colu−colv|=1 for each edge (u,v)(u,v) , where colxcolx is the label of vertex xx .

If there are multiple valid labelings, print any of them.

Input

The first line contains two integers nn and mm (1n50001≤n≤5000 ; 0m1050≤m≤105 ) — the number of vertices and edges in the graph.

The second line contains three integers n1n1 , n2n2 and n3n3 (0n1,n2,n3n0≤n1,n2,n3≤n ) — the number of labels 1, 2 and 3, respectively. It's guaranteed that n1+n2+n3=nn1+n2+n3=n .

Next mm lines contan description of edges: the ii -th line contains two integers uiui , vivi (1ui,vin1≤ui,vi≤n ; uiviui≠vi ) — the vertices the ii -th edge connects. It's guaranteed that the graph doesn't contain self-loops or multiple edges.

Output

If valid labeling exists then print "YES" (without quotes) in the first line. In the second line print string of length nn consisting of 1, 2 and 3. The ii -th letter should be equal to the label of the ii -th vertex.

If there is no valid labeling, print "NO" (without quotes).

Examples
Input
Copy
6 3
2 2 2
3 1
5 4
2 5
Output
Copy
YES
112323
Input
Copy
5 9
0 2 3
1 2
1 3
1 5
2 3
2 4
2 5
3 4
3 5
4 5
Output
Copy
NO

题目大意:给一张图(可能不连通)每个点可以涂1或2或3,要满足相邻点的值的差的绝对值等于1,可行的话输出方案。

貌似没有很多这个题的博客就说一下我的思路,不过感觉并不好而且代码不好写

首先我们能注意到这样一个事实:1和3实际上是没有区别的,因为1和3都只能连2,而2可以连接1或者3,那么我们就可以把1和3看作一类,把2看作另一类。

然后就有了这个结论:对于一个连通图来说,如果其存在奇环,那么一定是NO的(不理解可以画一个七边形,从一个点出发交替填0和1模拟一下)。

这样一来就好办了,对于输入数据建无向图(记得数组开两倍大小),进行一次或多次DFS(取决于有几个连通块),把每个连通块的点分成两大类(分别标记为0和1)。

至于从哪个点开始,哪个点标记成0哪个点标记成1是没有关系的,只要满足每条边两端点的标记不相同即可。

然后整个问题转化成一个DP问题:对于每个连通块我们可以选择标记为0的点来填上2也可以选择标记为1的点来填上2,

那么只要所有的连通块填上2的点的数目等于n2就是YES(剩下的随便填1或者3),这实际上是判断可行性,如果可行的话还需要记录路径。

不妨设DP[i][j]表示从1到第i个结构体能否选出j个点涂成2。最终如果DP[cnt][n2]为真则是YES的(cnt表示连通块的数目)。

转移方程: DP[i][j] |= (DP[i-1][j-b[i].zero] | DP[i-1][j-b[i].one]) 其中b[i].zero表示第i个连通块里标记为0的点的个数,one同理.

但这样的DP数组是不能满足我们的要求的,因为无法记录路径,这其实好办,只需要把DP数组的int类型换成自定义的结构体类型。

结构体不仅存储布尔值,还要存储如果布尔值为true的话是从上一个阶段哪个位置转移过来的,这样只需要从DP[cnt][n2]沿着记录的路径回溯回去就行了(因此也不好把数组压缩成一维的了),

回溯时,对于某个连通块里的点按照DP时为true的状态把相应的字符串的位置直接填上2,整个填完后再从头找字符串为空的地方随便填1和3即可。

#include <bits/stdc++.h>
#define N 5005
#define M 200005//要开两倍大小存反向边 
using namespace std;
int n,m,n1,n2,n3,tot=0,head[N],ver[M],Next[M];
int cnt=0;//belong[i]为i所属的连通块编号 
bool mark[N];//mark[i]为1 or 0代表i在所处的连通块里涂哪一类颜色 
vector<vector<int> >v; //确定每个连通块对应的节点 
bool flag=1,belong[N]={0};//belong[i]表示这个点是否被访问过 
char s[5005];//存储答案 
struct block
{
    int zero;//连通块里标记为0的点的个数 
    int one;
}b[N];
struct node
{
    bool ok;//判断可行性 
    //y 表示从上一层哪个位置转移过来的 num表示这个连通块标记为num的点应该涂成2 
    bool num;
    int y;
}**dp;//dp[i][j]表示
void add(int x,int y)
{
    ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
void dfs(int x,int pre,int num,bool color)//此处的color只有0和1两类(0的话要么涂2要么涂1 3) 
{
    int i;
    mark[x]=color;
    if(color==0) b[num].zero++;//统计每个连通块里0的数量 
    else b[num].one++;
    color=1-color;//下一层要换过来 
    belong[x]=1;//访问过 
    v[num].push_back(x);//对于每个连通块把点给塞进去 
    for(i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==pre)continue;
        if(!belong[y])
        {
            dfs(y,x,num,color);
        }
        else
        {
            if(mark[y]==mark[x])flag=0;//00碰头或者11碰头 
        }
    }
}
void draw(int nx,int ny)//涂色 
{
    int i;
    for(i=0;i<v[nx].size();i++)
    {
        if(dp[nx][ny].num==0)//如果选择把这个连通块标记为0的点涂上2 
        {
            if(mark[v[nx][i]]==0) s[v[nx][i]]='2';
        }
        else if(dp[nx][ny].num==1)
        {
            if(mark[v[nx][i]]==1) s[v[nx][i]]='2';
        }
    }
    if(nx==1)return;//边界 
    else draw(nx-1,dp[nx][ny].y);
}
 
int main()
{
    cin>>n>>m;
    cin>>n1>>n2>>n3;
    int i,j;
    for(i=1;i<=n;i++)
    {
        b[i].zero=b[i].one=0;
    }
    for(i=1;i<=m;i++)
    {
        int x,y;
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    if(m==0)//没有边全是点 一定可以 
    {
        for(i=1;i<=n;i++)
        {
            if(n1)s[i]='1',n1--;
            else if(n2) s[i]='2',n2--;
            else s[i]='3',n3--;    
        }
        cout<<"YES"<<endl;
        for(i=1;i<=n;i++)
        {
            cout<<s[i];
        }
        return 0;
    }
    else
    {
        if(!n2)//有边且n2=0 一定不可以 
        {
            cout<<"NO";
            return 0;
        }
        else if(n1==0&&n2==0 || n2==0&&n3==0 || n1==0&&n3==0)//有边且两个数为0 一定不可以 
        {
            cout<<"NO";
            return 0;
        }
    }
    vector<int> temp;
    v.push_back(temp);//凑数的 
    for(i=1;i<=n;i++)
    {
        if(!belong[i])
        {
            vector<int> temp;
            v.push_back(temp);
            cnt++;//连通块++ 
            dfs(i,0,cnt,0);
            if(!flag)
            {
                cout<<"NO";
                return 0; 
            }
        }
    }
     dp = new node *[cnt+4];//懒得改了 之前莫名其妙炸空间 其实不这么写 
    for (i = 0; i <= cnt; i++) 
    {
        dp[i] = new node [n+4];
    }
    //
    for(i=0;i<=cnt;i++)
    {
        for(j=0;j<=n;j++)dp[i][j].ok=0;
    }
    dp[1][b[1].zero].ok=1;//初始化 
    dp[1][b[1].zero].num=0;
    dp[1][b[1].one].ok=1;
    dp[1][b[1].one].num=1;
    for(i=2;i<=cnt;i++)
    {
 
        for(j=b[i].zero;j<=n;j++)
        {
            if(dp[i-1][j-b[i].zero].ok)
            {
                dp[i][j].ok=1;
                dp[i][j].y=j-b[i].zero;//从哪转移过来 
                dp[i][j].num=0;//i这个连通块的所有标记为0的点填2 
            }
        }
        //最好合起来写 但确定j的起始值写起来比较啰嗦 
        for(j=b[i].one;j<=n;j++)
        {
            if(dp[i-1][j-b[i].one].ok)
            {
                dp[i][j].ok=1;
                dp[i][j].y=j-b[i].one;
                dp[i][j].num=1;//i这个连通块的所有1的位置填2 
            }
        }
    }
     
    if(dp[cnt][n2].ok)//能凑出来n2 
    {
        draw(cnt,n2);//开始涂色 
    }
    else
    {
        cout<<"NO";
        return 0;
    }
    
    for(i=1;i<=n;i++)//涂剩下的1和3 
    {
        if(s[i]!='2')
        {
            if(n1)s[i]='1',n1--;
            else s[i]='3',n3--;        
        }
    }
    cout<<"YES"<<endl;
    for(i=1;i<=n;i++)cout<<s[i];
}
 

猜你喜欢

转载自www.cnblogs.com/lipoicyclic/p/12913950.html