zoukankan      html  css  js  c++  java
  • 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];
    }
     
  • 相关阅读:
    Filezilla账号设置多个文件夹
    VS中使用RDLC提示类型不一致
    VS批量添加多个文件
    SQLServer访问WebServices提示:SQL Server 阻止了对组件 'Ole Automation Procedures' 的 过程'sys.sp_OACreate' 的访问
    C#中Newtonsoft.Json 序列化和反序列化 时间格式
    C#注册OCX控件
    js中Tabs插件打开的标签页过多自动关闭
    web项目中使用火狐浏览器导出文件时文件名乱码
    html中table表格标题固定表数据行出现滚动条
    使用js方法将table表格中指定列指定行中相同内容的单元格进行合并操作。
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/12913950.html
Copyright © 2011-2022 走看看