Luogu 1258 队员分组

题目链接: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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值