UVa 1627 - Team them up! (二分圖染色 + 01揹包)
阿新 • • 發佈:2021-07-27
題目連結: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; }