大体题意:
告诉你n 个人的关系,要求所有的人分到两个组里,要求一个组里的人相互都认识。 每个人都必须要分到一个组里。 最后要求两个组的人员差值最小。最后输出两个组的人员。顺序随便输出即可。
思路:
当i 和j 不满足互相认识的话,就连接一条边,把能连通的点压到一个连通块里。
这样会得到k 个连通分量。
这样 就可以给每一个连通分量 进行二分图染色,当染色矛盾时,一定No solution了。(因为分组会出现矛盾)
这样每个连通分量 拆成两个值 a和-a ,|a| = |sum1 - sum0| 染成1的数量 和 染成0 的数量的差值。
这样我们就会得到2k 组个值, 每一组都要选一个数。求总和最接近0即可了。
这样就转换成了一个类似01背包问题。 因为价值有正有负, 但价值总和不会超过100,因此给每个价值加100即可。
每一层转移 在开一个cur 数组 记录选哪一个就可以打印解了。
写的比较乱 大体借鉴一下把。
吐槽::!!
一开始超时 以为乱写的太乱 超时了, 优化了很多地方,还是超时 发现是二分图染色超时了。
之后又不断的WA,是dp写错了。
给大家提供一个不错的数据:
12
3 4 5 6 7 8 9 10 11 12 0
4 5 6 7 8 9 10 11 12 0
1 4 5 6 7 8 9 10 11 12 0
1 2 3 6 7 8 9 10 11 12 0
1 2 3 8 9 10 11 12 0
1 2 3 4 7 8 9 10 11 12 0
1 2 3 4 6 8 9 10 11 12 0
1 2 3 4 5 6 7 0
1 2 3 4 5 6 7 10 11 12 0
1 2 3 4 5 6 7 9 11 12 0
1 2 3 4 5 6 7 9 10 12 0
1 2 3 4 5 6 7 9 10 11 0
ans =
6 9 10 11 12 5 2
6 8 4 6 7 1 3
一开始dp 写的是贪心,这个数据可以检测dp 写的是否正确= =!
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <string>
#define Siz(x) (int)x.size()
using namespace std;
///
inline bool scan_d(int &ret) {
char c; int sgn;
if(c=getchar(),c==EOF) return 0; //EOF
while(c!='-'&&(c<'0'||c>'9')) c=getchar();
sgn=(c=='-')?-1:1;
ret=(c=='-')?0:(c-'0');
while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
ret*=sgn;
return 1;
}
inline void out(int x) {
if(x>9) out(x/10);
putchar(x%10+'0');
}
///
int T, n;
int g[107][107];///建图
int f[107];///并查集
int find(int x){
return f[x] == x ? f[x] : f[x] = find(f[x]);
}
int cnt;
vector<int>G[107];///每个连通块存的点集
int color[107]; ///每个点染成什么颜色。
int Zero[107]; ///Zero[i]表示第i 个连通分量染色0的数量
int One[107]; ///One[i]表示第i 个连通分量染色1的数量
int w[107<<2][2];/// 每组的价值[0]和[1]
int id;
int flag[107];///把并查集父节点离散化。
int curdfs;///当前枚举的连通分量
void init(){
cnt = curdfs = id = 0;
memset(g,0,sizeof g);
}
void add(int x,int y){ f[find(x)] = find(y); }
int Ti = 0;
bool dfs(int x,int fa,int c){ /// 二分图染色
// Ti++;
// if (Ti > 100000)printf("%d\n",1/0);
if (color[x] == -1) {
color[x] = c;
if (c == 1)One[curdfs]++;
else Zero[curdfs]++;
}
else {
if (color[x] != c) return false;
return true;
}
for (int i = 1; i <= n; ++i){
if (!(g[x][i] == 1 && g[i][x] == 1) && fa != i && i != x){
if (!dfs(i,x,c^1)) return false;
}
}
return true;
}
int ABS(int t){
if (t < 0) return -t;
return t;
}
int dp[107][207],cur[107][207];///dp[i][j] = 1,表示第i 层价值为j 这个状态存在,0表示不存在。
///cur = 1 表示选负数那一组,为0 表示选正数那一组。
int ks;
vector<int>g0;/// 第一组的成员
vector<int>g1;/// 第二组的成员
///总和的绝对值不会超过100
void print(){
out(Siz(g0));
for (int i = 0; i < Siz(g0); ++i) {
putchar(' ');
out(g0[i]);
} putchar('\n');
out(Siz(g1));
for (int i = 0; i < Siz(g1); ++i) {
putchar(' ');
out(g1[i]);
}
putchar('\n');
}
void addg(int v){///打印解
g0.clear(); g1.clear();
int pos = id-1;
while(~pos){
if (cur[pos][v]){
if (Zero[pos] > One[pos]){
for (int i = 0; i < Siz(G[pos]); ++i){
int u = G[pos][i];
if (color[u] == 0) g0.push_back(u);
else g1.push_back(u);
}
}
else {
for (int i = 0; i < Siz(G[pos]); ++i){
int u = G[pos][i];
if (color[u] == 1) g0.push_back(u);
else g1.push_back(u);
}
}
}
else {
if (Zero[pos] > One[pos]){
for (int i = 0; i < Siz(G[pos]); ++i){
int u = G[pos][i];
if (color[u] == 1) g0.push_back(u);
else g1.push_back(u);
}
}
else {
for (int i = 0; i < Siz(G[pos]); ++i){
int u = G[pos][i];
if (color[u] == 0) g0.push_back(u);
else g1.push_back(u);
}
}
}
int iid = cur[pos][v];
v-=w[pos][iid];
--pos;
}
print();
}
void solve(){
memset(dp[0],0,sizeof dp[0]);
dp[0][w[0][0]+100 ] = 1; cur[0][w[0][0]+100 ] = 0;
dp[0][w[0][1]+100 ] = 1; cur[0][w[0][1]+100 ] = 1;
for (int i = 1; i < id; ++i){
memset(dp[i],0,sizeof dp[i]);
for (int j = 0; j <= 200; ++j){
if (dp[i-1][j] != 0){
if (j+w[i][0] >= 0 && j +w[i][0] <= 200){dp[i][j+w[i][0] ] = 1; cur[i][j+w[i][0] ] = 0;}
if (j+w[i][1] >= 0 &&j + w[i][1] <= 200){dp[i][j+w[i][1] ] = 1; cur[i][j+w[i][1] ] = 1;}
}
}
}
for (int i = 100; i <= 200; ++i){
if (dp[id-1][i]){
addg(i); return;
}
if (dp[id-1][200-i]) {
addg(200-i); return;
}
}
}
int main(){
scan_d(T);
while(T--){
// Ti = 0;
scan_d(n);
init();
G[0].clear();
for (int i = 1; i <= n; ++i){
int x;
color[i] = flag[i] = -1;
f[i] = i;
G[i].clear();
while(scan_d(x) && x){
g[i][x] = 1;
}
}
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
if (i != j){
if ( g[i][j] != 1 || g[j][i] != 1) add(i,j);
}
}
}
// puts("pout:");
for (int i = 1; i <= n; ++i){
int faa = find(i);
if (flag[faa] == -1) flag[faa] = cnt++;
G[flag[faa] ].push_back(i);
}
bool ok = 1;
for (int i = 0; i < cnt; ++i){
curdfs = i;
Zero[i] = One[i] = 0;
if(!dfs(G[i][0],-1,0)){
ok = 0;
break;
}
// printf("%d %d\n",Zero[i],One[i]);
int sum0 = Zero[i],sum1 = One[i];
int t1 = sum1 - sum0;
int t2 = sum0 - sum1;
if (t1 < t2) swap(t1,t2);/// 存取规定个规则就好了,我们规定左边存大值,右边存小值,差值为右边减左边
// printf("### %d %d\n",t1,t2);
w[id][0] = t1;
w[id++][1] = t2;
}
if (ks++)putchar('\n');
if (!ok) {
puts("No solution");
continue;
}
// puts("the color:");
// for (int i = 1; i <= n; ++i)printf("%d : %d\n",i,color[i]);
solve();
}
return 0;
}
/**
3
5
3 4 5 0
1 3 5 0
2 1 4 5 0
2 3 5 0
1 2 3 4 0
5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0
12
3 4 5 6 7 8 9 10 11 12 0
4 5 6 7 8 9 10 11 12 0
1 4 5 6 7 8 9 10 11 12 0
1 2 3 6 7 8 9 10 11 12 0
1 2 3 8 9 10 11 12 0
1 2 3 4 7 8 9 10 11 12 0
1 2 3 4 6 8 9 10 11 12 0
1 2 3 4 5 6 7 0
1 2 3 4 5 6 7 10 11 12 0
1 2 3 4 5 6 7 9 11 12 0
1 2 3 4 5 6 7 9 10 12 0
1 2 3 4 5 6 7 9 10 11 0
**/