Team them up!-团队分组(UVa1627)(黑白染色+Dp 0-1背包)

6 篇文章 0 订阅

前言

写了我好久……实现也比较丑陋……连续9行清空…输出也比较恶心

题目

传送门:
Vjudge
UVa(有点慢)
题目
题目大意:
有n(n<=100)个人,把他们分成非空的两组,使得每个人都被分到一组,且在同一组中的人互相认识,要求两组成员人数尽量接近,多解时输出任意一组方案,无解时输出No Solution.
输入时首先输入测试组数T,然后输入n,接下来n行第i行是对每个人i来说所认识的人的描述(读入0结束)
输出两行,每行首先输出每组人数,然后接着输出每组人的编号
in i n

2

5
3 4 5 0
1 3 5 0
2 1 4 5 0
2 3 5 0
1 2 3 4 0

5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0

out o u t

No Solution

3 1 3 5
2 2 4

思路

这道题我们发现两个人即使互相认识也不一定分到同一组,而两个人不认识(包括a认识b,b不认识a),也就是说两人互相不认识对于答案约束力比两人认识大,所以我们可以把两人互相不认识的关系看做一个图,我们可以得到一个或多个连通分量,如样例中的第二组数据,画出来的图如下:
图片
我们可以发现,相邻的点必然不会分到同一组,距离为2的点必然在同一组,我们可以用类似于二分图的方法来黑白染色,我们假定连通分量中编号最小点颜色为白色:
图片
那么如果出现环,并且返祖边的两个端点为同色,那么就No Solution,如样例一:
图片
好了我们假设在单个连通分量中黑点有x个,白点有y个,那么我们就是把黑点给1组或者把y点给1组,而我们的目标就是1组和2组人数差最小,那我们令X为1组人数,Y为2组人数,我们要的是 min{abs(XY)} m i n { a b s ( X − Y ) }
我们就可以想到这是一个0-1背包,我们把每个连通分量看作一件物品,
而这里单个物品的重量就是 abs(xy) a b s ( x − y ) ,我们针对背包重量可以加上 abs(xy) a b s ( x − y ) 也可以减去,我们的目标就是要让整个背包重量接近0,并且必须都放.
输出答案时我们用两种Vector,一种用来存储白点的人员编号,另一重存储黑点人员编号,Dp存储路径完后完后方便输出答案了
这道题主体思路就是这样,但实际上处理输出方案还是非常非常恶心的…

细节提示

1.这里0-1背包由于可加可减,所以我们要平移数组,我们平移输入的 n n 的长度,那我们最后在0 2n找合法的且与最近n的 f[n][i] f [ n ] [ i ] 就可以了
2.这里要保存路径,Dp完后找答案,所以不能开一维….
3.在DFS,找连通块连通块时注意不是遇到返祖边(回边)就无解,只有颜色不同时才无解
4.对于记录人员分组,我是用Vector记录的,这样Dp完后存储路径比较方便.

代码

#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int read(){
    int f=1,x=0;char s=getchar();   
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}  
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
#define MAXN 100
#define INF 0x3f3f3f3f
bool Map[MAXN+5],g[MAXN+5][MAXN+5];
vector<int> G[MAXN+5],Ans,S1[MAXN+5],S2[MAXN+5];
int w[MAXN+5],f[MAXN+5][2*MAXN+5],fa[MAXN+5][2*MAXN+5],vis[MAXN+5];
int DFS(int u,int f,int fa,int t){
    vis[u]=1+f;//u当前节点,f:黑白点标记,fa:父亲,t:第t件物品(方案用)
    if(!f) S1[t].push_back(u);//黑白点分别存储方案
    else S2[t].push_back(u);
    int siz=G[u].size(),ret=0,flag=0;
    for(int i=0;i<siz;i++){
        int v=G[u][i];//有回边但合法
        if(v==fa||(vis[v]&&vis[v]!=vis[u])) continue;
        else if(vis[v]==vis[u]) return INF;//不合法返回特殊值无解
        int tmp=DFS(v,(f+1)%2,u,t);
        if(tmp>100||tmp<-100){//儿子有特殊值无解返回
            flag=1;
            break;
        }//统计黑白节点数量差
        ret+=tmp;
    }
    return flag==1?INF:(ret+(!f?1:-1));
}
int pr[MAXN+5];
int main(){
    int T=read();
    while(T--){
        bool flag=0;//len:物品个数
        int n=read(),len=0;
        Ans.clear();//初始化
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        memset(G,0,sizeof(G));//G[i]:与i相连的点
        memset(pr,0,sizeof(pr));
        memset(fa,0,sizeof(fa));
        memset(S1,0,sizeof(S1));
        memset(S2,0,sizeof(S2));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++){
            int x;
            while(1){
                x=read();
                if(!x) break;
                Map[x]=1;
            }//Map[x]:当前人i认识x(0,1表示)
            for(int j=1;j<=n;j++)//g[i][j]:i,j两个人是否已连边
                if(!Map[j]&&j!=i&&!g[i][j]&&!g[j][i]){//(0,1表示)
                    G[i].push_back(j);
                    G[j].push_back(i);
                    g[i][j]=g[j][i]=1;
                }
            memset(Map,0,sizeof(Map));
        }
        for(int i=1;i<=n;i++)
            if(!vis[i]){//vis[i]:当前点所在连通分量是否访问过
                ++len,w[len]=abs(DFS(i,0,0,len));//可以不要绝对值
                if(w[len]==INF){//无解
                    flag=1;
                    break;
                }
            }
        if(flag){//无解
            puts("No solution");
            if(T) puts("");
            continue;
        }//fa[i][j]:此时f[i][j]是由f[i-1][fa[i][j]]转移过来的
        f[0][n]=1;//f[i][j]:能否用前i个物品凑成重量为abs(j-n)
        for(int i=1;i<=len;i++){//重量:两组人数差值
            for(int j=0;j<=2*n;j++){//平移数组
                if(j>=w[i]&&f[i-1][j-w[i]])//分两种放法(放1组或2组)
                    f[i][j]|=1,fa[i][j]=j-w[i];//记录路径
                if(j+w[i]<=2*n&&f[i-1][j+w[i]])
                    f[i][j]|=1,fa[i][j]=j+w[i];
            }
        }
        int x=len,y;//找最优分配方案
        for(int i=0;i<=n;i++){
            if(f[len][n-i]){
                y=n-i;
                break;
            }
            if(f[len][n+i]){
                y=n+i;
                break;
            }
        }
        while(x){//寻找路径
            int ny=fa[x][y];
            int siz1=S1[x].size(),siz2=S2[x].size();
            if(y<ny){//此处有些繁琐
                if(siz1>siz2)
                    for(int i=0;i<siz2;i++)
                        Ans.push_back(S2[x][i]);
                else for(int i=0;i<siz1;i++)
                        Ans.push_back(S1[x][i]);
            }
            else{
                if(siz1<siz2)
                    for(int i=0;i<siz2;i++)
                        Ans.push_back(S2[x][i]);
                else for(int i=0;i<siz1;i++)
                        Ans.push_back(S1[x][i]);
            }
            x--,y=ny;
        }
        printf("%d",Ans.size());
        for(int i=0;i<int(Ans.size());i++)//标记已选的人编号
            printf(" %d",Ans[i]),pr[Ans[i]]=1;
        printf("\n%d",n-Ans.size());
        for(int i=1;i<=n;i++)
            if(!pr[i])//找未选的人
                printf(" %d",i);
        puts("");
        if(T) puts("");//恶心的输出格式
    }
    return 0;
}

题外话

我写完代码发现其实物品的重量可以为负,那么输出方案时两个Vector的比较应该方便一些.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值