题目链接:https://www.luogu.com.cn/problem/P1285
这是一道很好很好的题
看一眼题目,就发现可能与二分图有关
然后,经过一定时间的思考,你会发现,这道题好像并不能直接用图论算法或者动态规划解决
这时候,我们就要考虑,是不是有一些可以用来贪心的定理。
首先直观一般会想到关于可以分到一个集合的人的条件
然后就会发现,一个人与不认识他或他不认识的人是绝对不可以分到同一个集合,这样是不是可以看做一个无向的边,又是分为两个集合,二分图染色?
我们可以把每个人与不认识他或者他不认识的人都建一条无向边,然后我们发现一个联通块分出来的集合是一定的。
也就是说,现在问题转化为有若干个联通块,在每个联通块上选择元素为 0 / 1 0/1 0/1的集合,答案加上这个集合的个数,最终求的不变
这不就是一个上界设为
n
/
2
n/2
n/2的
01
01
01背包吗?
简单
d
p
dp
dp即可
C o d e Code Code
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 100;
int a[MAXN + 10][MAXN + 10], fa[MAXN + 10], vis[MAXN + 10], n, tot = 0;
int cnt[MAXN + 10][2], dp[MAXN + 10][MAXN + 10];
bool sf[MAXN + 10][MAXN + 10], ans[MAXN + 10], dfn[MAXN + 10], ok;
void dfs(int x){
if (!ok) return;
if (vis[x] == 1) ++cnt[tot][0];
else ++cnt[tot][1];
for (register int i = 1; i <= n; ++i){
if (a[x][i] && a[i][x] || i == x) continue;
if (!vis[i]){
vis[i] = 3 - vis[x], fa[i] = fa[x];
dfs(i);
continue;
}
if (vis[i] != vis[x]) continue;
ok = 0;
}
}
void dfs2(int x, int op){
if (vis[x] == op) ans[x] = 1;
dfn[x] = 1;
for (register int i = 1; i <= n; ++i){
if (a[x][i] && a[i][x] || i == x) continue;
if (!dfn[i]) dfs2(i, op);
}
}
void get_list(int x, int j){
if (!x) return;
int op = sf[x][j] + 1;
for (register int i = 1; i <= n; ++i){
if (fa[i] != x) continue;
dfs2(i, op);
break;
}
get_list(x - 1, j - cnt[x][op - 1]);
}
inline int read();
int main(){
//freopen ("std.in", "r", stdin);
//freopen ("std.out", "w", stdout);
n = read();
for (register int i = 1; i <= n; ++i){
int x = read();
while (x){
a[i][x] = 1;
x = read();
}
}
for (register int i = 1; i <= n; ++i){
if (vis[i]) continue;
vis[i] = 1; ok = 1; fa[i] = ++tot;
dfs(i);
if (!ok){
printf("No solution\n");
return 0;
}
}
dp[0][0] = 1;
for (register int i = 1; i <= tot; ++i){
for (register int k = 0; k <= 1; ++k)
for (register int j = cnt[i][k]; j <= n / 2; ++j){
if (dp[i][j] || !dp[i - 1][j - cnt[i][k]]) continue;
dp[i][j] = 1;
sf[i][j] = k;
}
}
int num;
for (register int i = n / 2; i >= 1; --i){
if (!dp[tot][i]) continue;
num = i;
get_list(tot, i);
break;
}
printf("%d ", num);
for (register int i = 1; i <= n; ++i)
if (ans[i]) printf("%d ", i);
printf("\n%d ", n - num);
for (register int i = 1; i <= n; ++i)
if (!ans[i]) printf("%d ", i);
return 0;
}
inline int read(){
int x = 0;
char c = getchar();
while (!isdigit(c))c = getchar();
while (isdigit(c))x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return x;
}