博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
图论+dp poj 1112 Team Them Up!
阅读量:4319 次
发布时间:2019-06-06

本文共 3302 字,大约阅读时间需要 11 分钟。

题目链接:

题目大意:

有编号为1~n的n个人,给出每个人认识的人的编号,注意A认识B,B不一定认识A,让你将所有的人分成两组,要求每组的人相互认识,且两组的人数要尽可能的接近。

求出每组的人的编号。

解题思路:

图论+背包(输出物品)。

相互认识的关系不好确定分组,如果转换思路,考虑相互不认识的情况就简单好多,如果A不认识B,且B不认识C,那么A和C必须分到同一组里。所以就想到了,连通分量的染色,相邻的两个染不同的颜色(0或1),每一个连通分量分成两组,并且相同颜色的人不能有边(一定要相互认识),容易知道不同连通分量的人一定相互认识,否则是连通的。

然后问题就转化为有多个连通分量,每个连通分量有两组,每组必须属于一个队,求两个队的人数差最小,并分别输出两队的人。

dp[i][j]表示到了第i个连通分量,且第一个队的人数为j时是否能够恰好凑齐。

ans[i][j]表示对应于状态dp[i][j]时的选择,0表示选择0颜色的节点,1表示选择1颜色的节点。

求出dp[num][]后,根据ans[num][]的值往前推,颜色选好后把所有的该颜色节点都加进去该队里去。

代码:

 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define eps 1e-6#define INF 0x1f1f1f1f#define PI acos(-1.0)#define ll __int64#define lson l,m,(rt<<1)#define rson m+1,r,(rt<<1)|1//#pragma comment(linker, "/STACK:1024000000,1024000000")using namespace std;//本题关键是建反图,将相互认识的情况,转化成相互不认识的情况//思维逆向,这样便于处理,因为不同的连通区一定相互认识,同一连通区内可以分层处理,相邻的一定不相互认识#define Maxn 110struct Node{ int cnt[2]; int sa[2][Maxn]; //存每个连通区间的两类人数}node[Maxn];bool kn[Maxn][Maxn],nn[Maxn][Maxn];bool vis[Maxn];int n,num;bool dp[Maxn][Maxn]; //dp[i]表示第一个team的人数int ans[Maxn][Maxn]; //记录到达第i个连通区且状态为j时第一个队的选择int aa[Maxn],bb[Maxn];//aa表示第一个队的组成成员,void dfs(int v,int p){ node[num].cnt[p]++; //该连通区该颜色的人数 node[num].sa[p][node[num].cnt[p]]=v;//标号 for(int i=1;i<=n;i++) { if(!nn[v][i]||vis[i]) continue; vis[i]=true; dfs(i,p^1); }}bool ok() //对每一个区间扫描是否有矛盾的{ for(int i=1;i<=num;i++) { for(int p=0;p<2;p++) { for(int k=1;k<=node[i].cnt[p];k++) for(int m=k+1;m<=node[i].cnt[p];m++) { int a=node[i].sa[p][k],b=node[i].sa[p][m]; if(nn[a][b]) //同一联通快内,同颜色不认识的话有矛盾 return false; } } } return true;}int main(){ int a; while(~scanf("%d",&n)) { memset(kn,false,sizeof(kn)); memset(nn,false,sizeof(nn)); for(int i=1;i<=n;i++) while(scanf("%d",&a)&&a) kn[i][a]=true; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!kn[i][j]||!kn[j][i]) nn[i][j]=nn[j][i]=true; //建无向反图 memset(vis,false,sizeof(vis)); num=0; //num表示连通区的个数 for(int i=1;i<=n;i++) //对每一个连通区间 染色 if(!vis[i]) { num++; vis[i]=true; node[num].cnt[0]=node[num].cnt[1]=0; dfs(i,0); } if(!ok()) { printf("No solution\n"); continue; } memset(dp,false,sizeof(dp)); memset(ans,0,sizeof(ans)); dp[0][0]=true; for(int i=1;i<=num;i++) //简单背包,每个连通分量每种颜色必须进一个小队 { for(int j=n;j>=min(node[i].cnt[0],node[i].cnt[1]);j--) //第一个背包 { if(!dp[i][j]&&j>=node[i].cnt[0]&&dp[i-1][j-node[i].cnt[0]]) dp[i][j]=true,ans[i][j]=0; if(!dp[i][j]&&j>=node[i].cnt[1]&&dp[i-1][j-node[i].cnt[1]]) dp[i][j]=true,ans[i][j]=1; } } int gap=n,temp1=0,temp2=0; for(int i=1;i<=n;i++) //求出 差值最小的 两支队伍数 if(dp[num][i]&&abs(i-(n-i))
=1;i--) { //printf(":::%d\n",ans[i][temp1]); if(ans[i][temp1]) //逆向输出,说明达到该状态第一队选择了第1种 { for(int j=1;j<=node[i].cnt[1];j++) aa[++p]=node[i].sa[1][j]; for(int j=1;j<=node[i].cnt[0];j++) bb[++q]=node[i].sa[0][j]; temp1-=node[i].cnt[1]; //注意第一队要减去选择了的人数 每个连通分量必须有人选, } //第一队选择了第0种 else { for(int j=1;j<=node[i].cnt[0];j++) aa[++p]=node[i].sa[0][j]; for(int j=1;j<=node[i].cnt[1];j++) bb[++q]=node[i].sa[1][j]; temp1-=node[i].cnt[0]; } } printf("%d",q); for(int i=1;i<=q;i++) printf(" %d",bb[i]); putchar('\n'); printf("%d",p); for(int i=1;i<=p;i++) printf(" %d",aa[i]); putchar('\n'); } } return 0;}

 

 

转载于:https://www.cnblogs.com/pangblog/p/3275618.html

你可能感兴趣的文章
Atitit.git的存储结构and 追踪
查看>>
atitit 读书与获取知识资料的attilax的总结.docx
查看>>
B站 React教程笔记day2(3)React-Redux
查看>>
找了一个api管理工具
查看>>
Part 2 - Fundamentals(4-10)
查看>>
使用Postmark测试后端存储性能
查看>>
NSTextView 文字链接的定制化
查看>>
第五天站立会议内容
查看>>
(转))iOS App上架AppStore 会遇到的坑
查看>>
做好产品
查看>>
项目管理经验
查看>>
JMeter响应数据出现乱码的处理-三种解决方式
查看>>
No qualifying bean of type available问题修复
查看>>
spfile
查看>>
Team Foundation Service更新:改善了导航和项目状态速查功能
查看>>
Cookie/Session机制具体解释
查看>>
ATMEGA16 IOport相关汇总
查看>>
JAVA基础-多线程
查看>>
面试题5:字符串替换空格
查看>>
[Codevs] 线段树练习5
查看>>