UVA 1627 Team them up!(二分图染色+并查集+背包)

40 篇文章 1 订阅
20 篇文章 0 订阅

大体题意:

告诉你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

**/



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值