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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37555704/article/details/81910739

前言

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

题目

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

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

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)}
我们就可以想到这是一个0-1背包,我们把每个连通分量看作一件物品,
而这里单个物品的重量就是abs(xy),我们针对背包重量可以加上abs(xy)也可以减去,我们的目标就是要让整个背包重量接近0,并且必须都放.
输出答案时我们用两种Vector,一种用来存储白点的人员编号,另一重存储黑点人员编号,Dp存储路径完后完后方便输出答案了
这道题主体思路就是这样,但实际上处理输出方案还是非常非常恶心的…

细节提示

1.这里0-1背包由于可加可减,所以我们要平移数组,我们平移输入的n的长度,那我们最后在0 2n找合法的且与最近n的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的比较应该方便一些.

展开阅读全文

Team Them Up!

03-06

Your task is to divide a number of persons into two teams, in such a way, that:nneveryone belongs to one of the teams;nnevery team has at least one member;nnevery person in the team knows every other person in his team;nnteams are as close in their sizes as possible.nnThis task may have many solutions. You are to find and output any solution, or to report that the solution does not exist.nnnInputnnFor simplicity, all persons are assigned a unique integer identifier from 1 to N.nnThe first line in the input file contains a single integer number N (2 <= N <= 100) - the total number of persons to divide into teams, followed by N lines - one line per person in ascending order of their identifiers. Each line contains the list of distinct numbers Aij (1 <= Aij <= N, Aij != i) separated by spaces. The list represents identifiers of persons that ith person knows. The list is terminated by 0.nnnOutputnnIf the solution to the problem does not exist, then write a single message "No solution" (without quotes) to the output file. Otherwise write a solution on two lines. On the first line of the output file write the number of persons in the first team, followed by the identifiers of persons in the first team, placing one space before each identifier. On the second line describe the second team in the same way. You may write teams and identifiers of persons in a team in any order.nnnThis problem contains multiple test cases!nnThe first line of a multiple input is an integer N, then a blank line followed by N input blocks. Each input block is in the format indicated in the problem description. There is a blank line between input blocks.nnThe output format consists of N output blocks. There is a blank line between output blocks.nnnSample Inputnn2nn5n3 4 5 0n1 3 5 0n2 1 4 5 0n2 3 5 0n1 2 3 4 0nn5n2 3 5 0n1 4 5 3 0n1 2 5 0n1 2 3 0n4 3 2 1 0nnnSample OutputnnNo solutionnn3 1 3 5n2 2 4n 问答

没有更多推荐了,返回首页