原題鏈接 https://vjudge.net/problem/UVA-1627
題目大意:
有(n<=100)個人,要求把他們分成兩組,使得每個人都被分到一組,要求同組的人互相認識,而且要求兩組的人數儘量的接近。
題目分析:
首先將不認識的人之間連線,可以構造成一張圖,圖中可能有多個連通分量,可以對每個連通分量構造出二分圖。集合之中相互認識,集合之間不互相認識(就算a認識b,b不認識a也是兩個集合)。如果二分圖構造失敗,則表示無解,輸出“No solution”利用dfs構造二分圖,對每個點進行黑白染色,幫助判斷,並記錄二分圖數量。不同二分圖之間的人相互認識
for (int i = 1; i <= n; i++) {
if (!col[i]) {
num++;
set[num][0].clear();
set[num][1].clear();
dfs(i, 1);
}
}
void dfs(int p, int color) { //build bg
set[num][color-1].push_back(p);
col[p] = color;
for (int i = 0; i < g[p].size(); i++) {
int v = g[p][i];
if (!col[v]) {
dfs(v, 3-color); //v is i's stranger;
}
else {
if(col[v] == color)flag = false;
}
}
}
由於題目要求兩個數組之間的數量儘量相近,轉化成揹包問題就是將價值最大需求改成了兩集合之間的差值的絕對值最小。
因此可以將差值當做狀態進行枚舉(-100<=x<=100).對象爲每個二分圖。
則對於每個二分圖就有兩種選擇。將a集合放入ans0集合或放入ans1集合中(a爲第一個集合)
dp方程
dp[0][mask] = 1;
for (int i = 1; i <= num; i++) {
for (int j = -100; j <= 100; j++) {
if (dp[i-1][mask + j]) {
int d = set[i][0].size() - set[i][1].size();//二分圖的差值d
dp[i][mask + j + d] = 1;
dp[i][mask + j - d] = 2;
}
}
}
找出最後一個二分圖的最小差值, 向前遍歷,對於每一個二分圖 ,將a集合放入對應ans集合中,b集合放入另一個集合。
代碼如下
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
#pragma warning(disable:4996)
#define mask 100
using namespace std;
int n,num;//num 是連通分量的數目
int map[105][105],dp[105][205]; //dp 二分圖較多的集合所保存的數組下標
//g is stranger; set is bipartite graph
vector<int> g[105], set[105][2],ans[2];
int col[105]; //i's array(0,1);
bool flag;
void dfs(int p, int color) { //build bg
set[num][color-1].push_back(p);
col[p] = color;
for (int i = 0; i < g[p].size(); i++) {
int v = g[p][i];
if (!col[v]) {
dfs(v, 3-color); //v is i's stranger;
}
else {
if(col[v] == color)flag = false;
}
}
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n;
memset(dp, 0, sizeof(dp));
memset(col, 0, sizeof(col));
memset(map, 0, sizeof(map));
for (int i = 1; i <= n; i++) {
int a;
g[i].clear();
while(cin >> a&&a)
map[i][a] = 1;
}
flag = true;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (!(map[i][j]&&map[j][i])) {
g[i].push_back(j);
g[j].push_back(i);
}
}
}
num = 0;
for (int i = 1; i <= n; i++) {
if (!col[i]) {
num++;
set[num][0].clear();
set[num][1].clear();
dfs(i, 1);
}
}
if (!flag) {
cout << "No solution"<<endl;
if (t)printf("\n");
continue;
}
dp[0][mask] = 1;
for (int i = 1; i <= num; i++) {
for (int j = -100; j <= 100; j++) {
if (dp[i-1][mask + j]) {
int d = set[i][0].size() - set[i][1].size();
dp[i][mask + j + d] = 1;
dp[i][mask + j - d] = 2;
}
}
}
int ansd = 200;
for (int i = -100; i <= 100; i++) {
if (dp[num][i+mask] && abs(ansd) >abs(i) )
ansd = i;
}
ans[0].clear(); ans[1].clear();
for (int i = num; i >= 1; i--) {
int choice = dp[i][ansd + mask];
for (int j : set[i][0])
ans[choice == 1 ? 0 : 1].push_back(j);
for (int j : set[i][1])
ans[choice == 2 ? 0 : 1].push_back(j);
if (choice == 1)ansd -= set[i][0].size() - set[i][1].size();
else ansd += set[i][0].size() - set[i][1].size();
}
cout << ans[0].size()<<" ";
for (int i = 0; i < ans[0].size(); i++) {
if(i)
printf(" ");
printf("%d", ans[0][i]);
}
cout << endl<<ans[1].size()<<" ";
for (int i = 0; i < ans[1].size(); i++) {
if (i)
printf(" ");
printf("%d", ans[1][i]);
}
cout << endl;
if (t)cout << endl;
}
}
總結
這道題想了很久的時間,無果,果斷參考網上代碼,但大神的代碼不帶註釋的,看的我幾近放棄。不過這道題帶來的收穫也是巨大的,算是脫離了模板的01揹包dp方程。(萬萬沒想到居然能夠枚舉結果。),也瞭解二分圖和如何構造二分圖。競賽題果然涉及面極廣。