[线性DP] UVa1627 团队分组(二分图与背包)

题目

这里写图片描述

思路

前置知识,hdu4751二分图
首先本题跟hdu4751有点像,a和b不认识,b和c不认识,那么必然就有a和c在一个组。如果这时a和c再不认识,就是无解的情况。
所以上来首先给本题来个二分图判定,并分出连通块,此时的所有连通块都应当是二分图。
这里写图片描述
根据某一个连通块上的交叉染色,分出这个连通块的team0和team1。
那么现在就有两种选择,第一种是team0成为我们需要的team0,team1成team1。;一种是team1成team0,team0成team1。
这里写图片描述
最终我们是要让两队人数相同。我们算出每个连通块team0和team1的差值,这些差值加起来应该越靠近0越好。
这里就抽象出背包模型来了,只不过这次的总和不是某个值而是0。


本题的代码实现较复杂,有很多细节值得学习,而print函数也是懒得写搬过来LRJ,今后应该多看看。。

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 100 + 10;
int n, cc, know[maxn][maxn], G[maxn][maxn], color[maxn], d[maxn][3*maxn], diff[maxn];
vector<int> team[maxn][2];  // team[i][j]表示第i个连通图里的分组为j(0或1)的组员们

bool bfs(int u, int c, int cnt) {
    color[u] = c;
    team[cnt][c - 1].push_back(u);
    _rep(v,1,n)
        if (v != u && G[u][v]) {
            if (color[v] == c) return false;
            if (color[v] == 0 && !bfs(v, 3 - c, cnt)) return false;
        }
    return true;
}

bool erfen() {
    cc = 0;
    memset(color, 0, sizeof(color));
    memset(diff, 0, sizeof(diff));
    _rep(i, 1, n)
        if (color[i] == 0) {
            team[cc][0].clear();
            team[cc][1].clear();
            if (!bfs(i, 1, cc))
                return false;
            diff[cc] = team[cc][0].size() - team[cc][1].size();
            cc++;
        }
    return true;
}

void print(int ans) {
    vector<int> team1, team2;
    for (int i = cc - 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++)
            team1.push_back(team[i][t][j]);
        for (int j = 0; j < team[i][1 ^ t].size(); j++)
            team2.push_back(team[i][1 ^ t][j]);
    }
    printf("%d", team1.size());
    for (int i = 0; i < team1.size(); i++) printf(" %d", team1[i]);
    printf("\n");

    printf("%d", team2.size());
    for (int i = 0; i < team2.size(); i++) printf(" %d", team2[i]);
    printf("\n");
}

void dp() {
    memset(d, 0, sizeof(d));
    d[0][0 + n] = 1;  // 负下标访问问题
    _for(i, 0, cc) {
        _rep(j, -n, n) if(d[i][j+n]) {
            d[i+1][j + diff[i] + n] = 1;
            d[i+1][j - diff[i] + n] = 1;
        }
    }
    for (int ans = 0; ans <= n; ans++) {
        if (d[cc][ans + n]) { print(ans); return; }
        if (d[cc][-ans + n]) { print(-ans); return; }
    }
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        memset(know, 0, sizeof(know));
        memset(G, 0, sizeof(G));

        scanf("%d", &n);
        int v;
        _rep(u, 1, n) {
            while (1) {
                scanf("%d", &v);
                if (v == 0) break;
                know[u][v] = 1;
            }
        }

        // 根据不认识关系建图
        _rep(u, 1, n)
            _rep(v, 1, n)
                if(u!=v)
                    if (!know[u][v] || !know[v][u])
                        G[u][v] = G[v][u] = 1;

        if (!erfen()) printf("No solution\n");
        else {
            dp();
        }
        if (T) printf("\n");
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/81042940