Uva1627团队分组(二分图+DP)
题意:
思路:
预备知识:二分图。
-
只要A,B不是互相认识,那么肯定在不同的大组里。先根据不认识的关系建立图(只要a,b不是互相认识他们之间就有一条边),这个图可以有多个连通分量。
对于每个连通分量,如果是二分图,说明这个连通分量可以分成两个小组(1组和2组),且这两个小组最终会进入不同的大组(A组和B组)。如果不是二分图,说明总要有不认识的人在一个小组,存在矛盾,无解。 -
接下来,需要在每个连通分量里的2个小组(1,2)分别放入两个大组(A,B),类似0-1背包。
-
设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为连通分量个数)。
-
输出方案,从后向前判断设当前差值为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