Team them up! UVA - 1627(动态规划+好题)

it2026-03-03  3

题目 题目大意:

给你n个数字,让你将数字分成两组。是的两组的数字个数尽可能相近。规则如下:每个数字有各自认识的其他数字,每个数字只能和他认识的数字在一组。求分配方式。

题目分析:

从正面并不是很好下手,不过可以换过一个角度去思考。我们可以考虑如果1,2不认识的话,那么1,2,肯定在不同的组。我们可以把这种关系记录下来。如(1,2),(1,3)(3,4)不能在一组的话,那么只好2,3在一组,1,4在一组。我们可以把不能在一组的两个数字连上边,然后所以不能在一起的关系就会构成一个图。图中所有边都代表边上的两个点不能在一起。如果需要满足合法的情况的话,就需要这个图是二分染色图。就是相邻的点(同一条边)必须是不同的颜色。dfs判断一下就好。

建好图之后,因为这个图并不联通。就有可能有很多独立块。我们需要处理的就是这些独立块。每一个独立块都要可以二分染色,所以每一个独立块中的点都会被分成两个集合。我们用一个VECTOR记录下第几个独立块的两个独立块分别的数字有哪些。然后实际上变成了这样一个问题。现在有两个袋子,需要把每个独立块的两个集合分别装到这两个袋子里面。使得最后袋子中的数字个数差不多。是不是很像01背包?

由于考虑两个袋子情况太多,事实上只要确定一个袋子的情况就可以跟着确定另一个袋子的情况了。所以令DP[I][J]表示现在考虑到了第I个独立块,第一个袋子中的数字个数为J。最后的答案就是DP[k][j].k是独立块的个数,J是尽量接近n/2的一个数。

代码如下:

#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #include<queue> #include<stack> #include<vector> using namespace std; const int maxx = 105; const int INF = 0x3f3f3f3f; typedef long long LL; int t,n; int G[maxx][maxx];//存储图的数组 int vis[maxx];//判断这个数字是否已经被染色 vector<int>ve[maxx][2];//存储每个独立块的两个集合 int dp[maxx][maxx];//含义同上 bool fl;int k=0;//fl判断是够这个图合法,能够二分染色,K为独立块的数量 void init(){//初始化 memset(dp,0,sizeof(dp)); memset(G,0,sizeof(G)); for(int i=0;i<=n;i++)ve[i][0].clear(),ve[i][1].clear(); memset(vis,-1,sizeof(vis)); fl = true; k=0; } void dfs(int x,int fa,int e){//dfs得到每一个独立块的两个集合的数字 if(!fl)return ; ve[k][e].push_back(x);vis[x] = e;//已经被染色,以后不再考虑 for(int i=1;i<=n;i++){ if(fa==i||x==i)continue; if(vis[i]!=-1&&vis[i]!=e)continue; if(G[x][i]==0){ if(vis[i]==e){fl = false;return;} if(vis[i]==-1)dfs(i,x,e^1); } } } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n); init();int num; for(int u=1;u<=n;u++){ while(scanf("%d",&num)&&num!=0)G[u][num] = 1;//如果能联通 } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)if(G[i][j]==0)G[j][i]=0;//如果1认识3,但3不认识1.这两者也是不连通的。 } for(int i =1;i<=n;i++){ if(vis[i]==-1){k++;dfs(i,-1,0);} } if(!fl){printf("No solution\n");printf("\n");continue;} int aa = ve[1][0].size();int bb = ve[1][1].size(); dp[1][aa]=1;dp[1][bb] = 2; for(int i=2;i<=k;i++){//01背包问题,对于第i个独立块,有两个选择,将当前独立块的第一个集合或者是第二个集合装入第一个袋子中 aa = ve[i][0].size();bb = ve[i][1].size(); for(int j=0;j<maxx;j++){ if(dp[i-1][j]){dp[i][j+aa]=1;dp[i][j+bb]=2;}//用dp记录路径,等于1表示第I个独立块是把第一个集合装入了第一个袋子。等于2表示把第二个集合装入了第一个袋子。 } } // for(int i=1;i<=n;i++){ // for(int j=0;j<ve[i][0].size();j++)cout<<ve[i][0][j]<<" ";cout<<" 0"<<endl; // for(int j=0;j<ve[i][1].size();j++)cout<<ve[i][1][j]<<" ";cout<<" 1"<<endl; // } aa = n/2;int ans = 0; while(aa>=0){//判找到最接近n/2的第一个袋子中的数字数aa。 if(dp[k][aa]&&aa!=0)break; if(dp[k][n-aa]&&n!=aa){aa = n-aa;break;} aa--; } memset(vis,0,sizeof(vis)); printf("%d",aa);bb=aa; for(int i=k;i>=1;i--){//然后根据上面找到的aa,dp[i][aa]记录着第I块选的哪一个集合,减掉这个集合的数字可以得到DP[I-1][J]如此递推。 int f = dp[i][aa]-1; int sz = ve[i][f].size(); for(int j=0;j<sz;j++) vis[ve[i][f][j]]=1,printf(" %d",ve[i][f][j]); aa-=sz; } printf("\n"); printf("%d",n-bb); for(int i=1;i<=n;i++){ if(!vis[i])printf(" %d",i); } printf("\n");printf("\n");//记得每组数据之间的换行 } return 0; }
最新回复(0)