Uva1627团队分组(二分图+DP)

题意:

在这里插入图片描述

思路:

预备知识:二分图

  1. 只要A,B不是互相认识,那么肯定在不同的大组里。先根据不认识的关系建立图(只要a,b不是互相认识他们之间就有一条边),这个图可以有多个连通分量。
    对于每个连通分量,如果是二分图,说明这个连通分量可以分成两个小组(1组和2组),且这两个小组最终会进入不同的大组(A组和B组)。如果不是二分图,说明总要有不认识的人在一个小组,存在矛盾,无解。

  2. 接下来,需要在每个连通分量里的2个小组(1,2)分别放入两个大组(A,B),类似0-1背包。
    在这里插入图片描述

  3. 设dp[i][j]表示前i个连通分量,是否有差值为j的组合(j可正可负,代表A大于B或B大于A)。设同一连通分量i中的两个小组点数差值为diff[i],则若dp[i-1][j]==1,则dp[i][j+diff[i]]=1,dp[i][j-diff[i]]=1(即分到AB或BA)。最后判断是否存在最小的abs(j)使得dp[num][j]==1(num为连通分量个数)。

  4. 输出方案,从后向前判断设当前差值为ans,若dp[i-1][ans-diff[i]]==1成立代表将第i个连通分量两种点分为AB两组,同时ans-=diff[i];若dp[i-1][ans+diff[i]]==1成立代表将第i个连通分量中两种点分为BA两组,同时ans+=diff[i],记录即可。

核心操作是枚举差值。学不来学不来。。

#include <cstdio>
#include <vector>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f; 
const int maxn = 100+5;

int n, G[maxn][maxn], color[maxn], diff[maxn];
vector<int> team[maxn][2]; //  // team[i][j]表示第i个连通图里的分组为j(0或1)的成员
int cnt; // 连通分量的编号

// 二分图判定 。用1和2两种颜色染色。 
bool dfs(int u, int c){
	color[u] = c;
	team[cnt][c-1].push_back( u );
	for(int v = 0; v < n; ++v){
		if(u != v&&!(G[u][v]&&G[v][u])){ // 不是互相认识 
			if(color[v] > 0 && color[v] == color[u]) return false;
			if(!color[v] && !dfs(v, 3-c)) return false;
		}
	}
	return true;
}
// 根据不认识关系建图 
bool build_graph(){
	memset(color,0,sizeof(color));
	cnt = 0; 
	for(int i = 0; i < n; ++i){
		if(!color[i]){
			team[cnt][0].clear();
			team[cnt][1].clear();
			if(!dfs(i,1)) return false;
			diff[cnt] = team[cnt][0].size() - team[cnt][1].size(); // 连通分量内部的两个不同染色部分的数量差 
      		++cnt;
		}
	} 
	return true;
}

int d[maxn][maxn*2];

void print(int ans){
	vector<int> teamA, teamB;
	for(int i = cnt - 1; i >= 0; --i){
		int t;
		if(d[i][ans-diff[i]+n]) { t = 0; ans -= diff[i];}
		else { t = 1; ans += diff[i];}
		for(int j = 0; j < team[i][t].size(); ++j)
			teamA.push_back( team[i][t][j] ); 
		for(int j = 0; j < team[i][1^t].size(); ++j)
			teamB.push_back( team[i][1^t][j] );
	}
	
	printf("%d", teamA.size());
    for(int i = 0; i < teamA.size(); i++) printf(" %d", teamA[i]+1);
    printf("\n");
    
    printf("%d", teamB.size());
    for(int i = 0; i < teamB.size(); i++) printf(" %d", teamB[i]+1);
    printf("\n");
}


void dp(){
	memset(d,0,sizeof(d));
	d[0][0+n] = 1; // 加n是为了防止负的下标越界
	for(int i = 0; i < n; ++i){
		for(int j = -n; j < n; ++j) if(d[i][j+n]){ // 枚举差值
			d[i+1][j+diff[i]+n] = 1; // 0小组 -> A, 1小组 -> B大组
			d[i+1][j-diff[i]+n] = 1; // 0->B, 1->A
		}
	} 
	for(int ans = 0; ans < n; ++ans){
		if(d[cnt][ans+n]){ print(ans); return; }
		if(d[cnt][-ans+n]){ print(-ans); return; }
	}
}

int main()
{
	freopen("in.txt","r",stdin);
	int T; scanf("%d",&T); 
	while(T--){
		scanf("%d",&n);
		memset(G,0,sizeof(G));
		for(int u = 0; u < n; ++u){
			int v; 
			while(scanf("%d",&v)==1&&v)	G[u][v-1] = 1;
		}
		
		if(n == 1 || !build_graph()) printf("No solution\n");
		else dp();
		
		if(T) printf("\n");
	}

	return 0;
}

参考:https://blog.csdn.net/wang2147483647/article/details/77723438

猜你喜欢

转载自blog.csdn.net/CY05627/article/details/88698798