原题链接 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方程。(万万没想到居然能够枚举结果。),也了解二分图和如何构造二分图。竞赛题果然涉及面极广。