1. 程式人生 > 其它 >UVa 1627 - Team them up! (二分圖染色 + 01揹包)

UVa 1627 - Team them up! (二分圖染色 + 01揹包)

題目連結:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4502

題目大意:

\(n\) 個人分成兩組,每組的人互相認識,每個人都在一個組中,且每個組中至少有一個人,要求兩個組的人數之差儘量小,求分組方案。

題解:

認識的人不一定在同一個組裡,但不認識的人一定不在同一個組裡,所以可以將不認識的人互相連邊,於是轉化成二分圖判定,如果不是二分圖,則該問題無解。

將連通分量染色後,就是該連通分量的分組方案,顏色還可以翻轉過來,也就是分組可以互換。這樣我們記錄一下每個連通分量中兩個組的人數差值是多少,在所有連通塊中選擇出差值絕對值最小的方案。這個問題類似 \(01\)

揹包,每個連通塊有兩個決策,每種決策對應一個“體積”,要求選擇體積儘可能小的方案。
\(dp[i][j]\) 表示選前 \(i\) 個連通塊,兩組人數相差 \(j\) 的方案是否可行,轉移時再記錄一下決策即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 105;
const int INF = 1000000007;

int T, n;
int g[maxn][maxn], G[maxn][maxn], d[2][maxn];
int color[2][maxn], cc[maxn];
int dp[maxn][5*maxn], pre[maxn][5*maxn];
int flag, tot;
vector<int> ttm[2][maxn], a1, a2;

bool dfs(int u, int block, int t){
	if(color[t][u] == 1) {
		++d[t][block];
		ttm[t][block].push_back(u);
	}
	else {
		--d[t][block];
		ttm[t][block].push_back(u);
	}
	for(int v = 1 ; v <= n ; ++v){
		if(G[u][v]){
			if(color[t][v] == color[t][u]) return false;
			if(!color[t][v]){
				color[t][v] = 3 - color[t][u];
				if(!dfs(v, block, t)) return false;
			} 
		}
	}
	return true;
} 

ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }

int main(){
	int f = 0;
	scanf("%d", &T);
	while(T--){
		if(f) printf("\n");
		f = 1;
		flag = 1;
		tot = 0;
		a1.clear(), a2.clear();
		memset(pre, 0, sizeof(pre));
		memset(dp, 0, sizeof(dp)); 
		memset(d, 0, sizeof(d));
		memset(color, 0, sizeof(color));
		memset(cc, 0, sizeof(cc));
		memset(g, 0, sizeof(g));
		memset(G, 0, sizeof(G));
		scanf("%d", &n);
		for(int i = 1 ; i <= n ; ++i) ttm[0][i].clear(), ttm[1][i].clear();
		int u;
		for(int i = 1 ; i <= n ; ++i){
			while(1){
				scanf("%d", &u);
				if(!u) break;
				g[i][u] = 1;
			}
		}
		
		for(int i = 1 ; i <= n ; ++i){
			for(int j = 1 ; j <= n ; ++j){
				if(i == j) continue;
				if(!g[i][j] || !g[j][i]) G[i][j] = G[j][i] = 1; // 不認識的人連邊
			}
		}
		
		for(int i = 1 ; i <= n ; ++i){
			if(!color[0][i]){
				color[0][i] = 1;
				++tot;
				if(!dfs(i, tot, 0)){
					printf("No solution\n");
					flag = 0;
					break;
				}
			}
		}
		
		if(flag){
			tot = 0;
			for(int i = 1 ; i <= n ; ++i){
				if(!color[1][i]){
					++tot;
					color[1][i] = 2;
					dfs(i, tot, 1);
				}
			}	
		}
		
		if(flag){ // 有解 
			dp[0][200] = 1;
			for(int i = 1 ; i <= tot ; ++i){
				for(int j = -n ; j <= n ; ++j){
					if(dp[i-1][j-d[0][i]+200]){
						pre[i][j+200] = 1;
						dp[i][j+200] |= dp[i-1][j-d[0][i]+200];	
					}
					if(dp[i-1][j-d[1][i]+200]){
						pre[i][j+200] = 2;
						dp[i][j+200] |= dp[i-1][j-d[1][i]+200];
					}
				}
			}
			
			int ans = INF;
			for(int i = 0 ; i <= n ; ++i){
				if(dp[tot][i+200] == 1){
					ans = i; break;
				}
				if(dp[tot][-i+200] == 1) {
					ans = i; break;
				}
			}
			
			int V = ans;
			for(int i = tot ; i >= 1 ; --i){
				if(pre[i][V+200] == 1){
					V = V - d[0][i];
					cc[i] = 1;
				} else if(pre[i][V+200] == 2){
					V = V - d[1][i];
					cc[i] = 2;
				}
			}
			
			int c1 = 0, c2 = 0;
			for(int i = 1 ; i <= tot ; ++i){
				if(cc[i] == 1){
					for(int j = 0 ; j < ttm[0][i].size(); ++j){
						if(color[0][ttm[0][i][j]] == 1){
							++c1; a1.push_back(ttm[0][i][j]);
						} else{
							++c2; a2.push_back(ttm[0][i][j]);
						}
					}
				} else{
					for(int j = 0 ; j < ttm[1][i].size(); ++j){
						if(color[1][ttm[1][i][j]] == 1){
							++c1; a1.push_back(ttm[1][i][j]);
						} else{
							++c2; a2.push_back(ttm[1][i][j]);
						}
					}					
				}
			}
			printf("%d ", c1);
			for(int i = 0 ; i < a1.size(); ++i){
				printf("%d ", a1[i]);
			} printf("\n");
			printf("%d ", c2);
			for(int i = 0 ; i < a2.size(); ++i){
				printf("%d ", a2[i]);
			} printf("\n");
		}
	}
	return 0;
}