POJ 1112 Team Them Up! (图(反图)染色+连通分量+动态规划) 解释代码

题目引用大家的

首先思想参考:http://www.cppblog.com/linyangfei/archive/2008/08/08/58295.html

代码参考:http://blog.csdn.net/behappyxiang/article/details/8484811

 

然后根据我个人的理解又把代码自己写了一遍,和上面的那个思想几乎一样,只是自我的解释更多了些。但大家不知道能不能看懂……

 

求完连通图后最后转为的动态规划模型就像

1.你有两个箱子(容量够大)。

2.有n种东西,每个可以拆成两部分。

3.每种东西必须完全放入箱子中,而且每部分必须放入不同的箱子中。

4.要求使两个箱子最后的质量尽量接近。

 

 

/*
题目大意:把n个人分成2各组,每一个人有他所认识的人。所分的组有四点要求:

1、 每个人都必需属于一个组。

2、 每个组至少有一个人。

3、 每个组里面的每个人必需互相认识。

4、 两个组的成员应尽量接近。

首先分析这道题目,题目给出的是一个有向图,即如果有A认识B,但不一定有B认识A。但是在所分配的组里面,任意两个人都要互相认识。

1、 先读入数据建立有向图,然后对这个有向图进行处理,如果两个点之间的边是单向边,就认为两个点之间无边(因为这两个人不互相认识),对于两个点间的双向边,即建立一条无向边(这两个人互相认识),这样就可以把一个有向图转化为一个无向图。

2、 将这个无向图转化为它的反图。即有边的把边删去,无边的添上一条边。(其实1,2步在程序实现时可以一次完成)。

3、 对转换后的反图求极大连通分量。想想就会明白刚才为什么要求反图,可以看到在这个反图中的不同的连通分量中的两个人都是互相认识的!!!接下来很关键,那些互不认识的人就在一个连通分量里面。

4、 在做DFS求连通分量的时候,同时对连通分量中的点染色(0或1),如果一个点颜色为0,那么所有和它相邻的点与它的颜色应该不同(标记为1)。这是因为反图中相邻的两个人都是不认识的(回顾刚才反图的作用)。
这样DFS结束后,就可以根据颜色把连通分量中的点分成两个组s0和s1,在不同两个组的人是不可能分到一个team内的。到这里要做一个特判,对于s0或s1组里的任意两个人p,q如果反图中存在一条边(p,q),说明无解,输出"No solution"。

******************************************************** 
我们应该将这个联通分量里的人染色成二分图(x,y),以便于分配到不同的队伍里。
(染色成二分图是为了使之满足题意的两个队伍之间人数最close)。
二分图xy里,x或者y组里的任意两个人如果在补集里面有边,
 
则输出"No solution"。(理解为无法染色吧)
 
原因是这时没办法分成两个队伍,起码要三个队伍。
********************************************************** 
 

5、 求出了所有的连通分量,对于第i个连通分量都把其节点分为两个组s1i和s2i。不同连通分量中的人都互相认识 
这时要做的就是把所有的s1i和s2i分配到team1和team2中去,使team1的总和与team2的总和差值最小。就可以用背包DP了。
dp[i][j]表示第i个连通分量达到j的差值,true为可达,false为不可达。
状态转移方程:  
dp[i][j+si0-si1]=true if dp[i-1][j] == true
dp[i][j-si0+si1]=true if dp[i-1][j] == true
同时记录转移路径,差值j的范围是-100~+100可以坐标平移。
最后在dp[m][i](m是最后一个连通块数)中找出值为true的最小i值。输出答案。


自我理解动归过程:
1.初始条件有个差值  表示此差值会发生 ,记住差值可能会增大也可能会减小(实际也是这样)所以 每个小组的两个要互减然后判断 
2.遍历所有差值的情况,如果发现有个差值已经被前面的连通分量占用那么在此基础上制造新的差值,并记录状态,不过此时之前要没有人记录过
3. 在所有差值中当然要找加入最后一个连通分量后 不加游标时差值靠近0的。 

*/ 
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
//数组大多是从1开始算起 
#define Max 201
int n;//respect the number of nodes
int node[Max][Max];//表示两个节点之间是否相连,(建图时巧妙的话可直接建立反图,我没有) 
int visit[Max]; //在DFS的时候看被访问过没有
int cnt;//记录连通图的个数 
int dp[Max][2*Max]; //后面有个处理可以使这个不超界 
struct
{
    int num[2];//num_1,num_2; 只有这个从0记,好染色 
    int data[2][Max/2+1];//之所以用二维数组,是因为好用 0 与 1来描述直接第一个下标 
    int sel;//记录输出时最先被选中的 
    int rdRoad[2*Max][2]; //记录这次被选中的num(染色的组)  及记录上一次的diff 
}s[Max/2+1];    
void dsf(int nd,int color)
{
    //cout<<"node: "<<nd<<endl;
    int i;
    visit[nd]=1;
    s[cnt].data[color][ ++s[cnt].num[color] ]=nd;//我要从1开始 
    for(i=1;i<=n;i++)
    {
        if(!visit[i] && node[nd][i]) dsf(i,color^1);
    }
}
bool check()  
{
    //检查反图,说明之前不认识的群体多了,无法分成两个组,反图中连接的两点一定不认识 
    //染色时颜色不一样  因此要查一下是不是真的相连 
    int i,j,k;
    for(i=1;i<=cnt;i++)
    {
        for(j=1;j<=s[i].num[0];j++)
        {
            for(k=j+1;k<=s[i].num[0];k++)
            {
                if( node[ s[i].data[0][j] ][ s[i].data[0][k] ] ) 
                {
                    cout<<"No solution"<<endl;
                    return false;
                }    
            } 
        }    
        
        for(j=1;j<=s[i].num[1];j++)
        {
            for(k=j+1;k<=s[i].num[1];k++)
            {
                if( node[ s[i].data[1][j] ][ s[i].data[1][k] ] ) 
                {
                    cout<<"No solution"<<endl;
                    return false;
                }    
            } 
        }  
        
    }    
    return true;
}
//内联函数为了代码简单 
inline int nm(int id,int type){ return s[id].num[type]; }  //0 或 1 
inline int nmdf(int id){ return s[id].num[0]-s[id].num[1]; } 
void DynamicProcess()
{
    memset(dp,0,sizeof(dp));
    dp[1][100+ nmdf(1)]=1; 
    s[1].rdRoad[100+ nmdf(1)][0]=0;
    s[1].rdRoad[100+ nmdf(1)][1]=100;
    dp[1][100- nmdf(1)]=1; 
    s[1].rdRoad[100- nmdf(1)][0]=1;
    s[1].rdRoad[100- nmdf(1)][1]=100;
    int i,j;
    for(i=2;i<=cnt;i++)
    {
        for(j=100+n;j>=100-n;j--) //倒着来保证不会使 temp出现负数 
        {
            if(dp[i-1][j])//之前这个差值存在  可以在此基础上制造新的差值
            {
                int temp= j+nmdf(i);
                if(!dp[i][temp])//自己现在制造的还没有人制造 
                {
                    dp[i][temp]=1; 
                    s[i].rdRoad[temp][0]=0;
                    s[i].rdRoad[temp][1]=j;//记录之前的差值 
                } 
                temp= j-nmdf(i);
                if(!dp[i][temp])//自己现在制造的还没有人制造 
                {
                    dp[i][temp]=1; 
                    s[i].rdRoad[temp][0]=1;
                    s[i].rdRoad[temp][1]=j;//记录之前的差值 
                } 
            } 
        }    
    }     
    
}     
void output()
{
    int i,j;
    for(j=0;j<=n;j++)//先找到路径的头 
    {
        if(dp[cnt][100+j])
        {
            j+=100;
            break;//最后那个加上后的差值 
        }    
        if(dp[cnt][100-j])
        {
            j=100-j;
            break;
        }    
    }    
    int num[2]={0};//记录总人数 
    int type;//记录染色类型 
    for(i=cnt;i>0;i--)//找出路径 
    {
        type=s[i].sel=s[i].rdRoad[j][0];
        num[0]+= nm(i,type);
        num[1]+= nm(i,type^1);
        j= s[i].rdRoad[j][1]; //找下一个差值
    }    
    
    printf("%d ",num[0]);
    for(i=1;i<=cnt;i++)
    {
        for(j=1;j<=nm(i,s[i].sel);j++)
        {
            printf("%d ",s[i].data[s[i].sel][j]);
        }    
    }    
    printf("\n%d ",num[1]); //输出余下的 
    for(i=1;i<=cnt;i++)
    {
        for(j=1;j<=nm(i,s[i].sel^1);j++)
        {
            printf("%d ",s[i].data[s[i].sel^1][j]);
        }    
    }   
    cout<<endl;
    //poj在此对输出的顺序没有要求  它有 特别的判断方法    
}    
int main()
{
    int i,j,k;
    while(cin>>n)
    {
        memset(node,0,sizeof(node)); //建图时利用了这里的初始值 
        for(i=1;i<=n;i++)//数据录入 
        {
            while(cin>>k && k)
            {
                if(i>k && node[k][i]==2)
                {
                    node[k][i]=node[i][k]=1;
                }    
                else  node[i][k]=2;
            }       
        }    
        
        for(i=1;i<=n;i++)//与上面一起建立反图 
        {
            for(j=1;j<=n;j++)
            {
                if(node[i][j]==1)node[i][j]=0;
                else node[i][j]=1;
                
                //cout<<i<<"  "<<j<<": "<< node[i][j]<<endl; 
            }    
        }    
        
        //找连通通路准备 
        memset(visit,0,sizeof(visit));
        cnt=0;
        for(i=1;i<=n;i++) 
        {
            if(!visit[i]) 
            {
                cnt++;
                s[cnt].num[0]=s[cnt].num[1]=0;
                dsf(i,0);//深搜 
            }    
        }    
        if(!check()) continue; //判断有无解    
         
        //动态规划问题   找到解 
        DynamicProcess();
        
        output(); 
        
    }    
    return 0;
}     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值