jzoj3782 [NOIP2014模拟8.17] 组队

123 篇文章 0 订阅
9 篇文章 0 订阅

Description


你的任务是将一群人分到两个队伍中,使得:

1、每个人都属于一个队伍。
2、每个队伍至少有一个人。
3、每个队伍的任意一个人都认识其他人。
4、两支队伍的人数尽可能接近。

这个任务可能有多组解,你可以输出任意一种。
注意:认识是单向的且没有传递性。

对于分值为 30%的数据,N <= 15
对于剩余分值为 70%的数据,N <= 100
[评分标准]

该题采用捆绑测试点评测的方式,即一组测试点包括许多测试点,对于每组测试点,你的必须通过该组测试点下的所有单个测试点才能得到全部分数,任意测试点错误都会导致该组测试点不得分。

该测试方式只为了提高测试准确度,并不会对选手的时间限制等造成影响。

即选手在编写程序时不需要考虑任何额外的操作。

Solution


考试的时候盯着100的n看光想最小割了
可以转化为经典的2-sat模型,若两人无论如何都不能分在一组就连一条无向边。对于每个连通块单独讨论,如果不能黑白染色就无解。dp[k][i][j]表示能否实现前k个连通块一组分了i人另一组分了j人,转移的时候记录方案即可

捆绑数据真恶心,只有四种得分

Code


#include <stdio.h>
#include <string.h>
#include <stack>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)

const int N=205;
const int E=50005;

struct edge{int x,y,next;}e[E];
bool rc[N][N],inStack[N],vis[N];
int dfn[N],low[N],scc[N];
int col[N],a[N],b[N];
int f[N][N][N],rec[N][N][N];
int ls[N],edCnt;
std:: stack<int> stack;
bool flag;

void addEdge(int x,int y) {
    e[++edCnt]=(edge){x,y,ls[x]}; ls[x]=edCnt;
    e[++edCnt]=(edge){y,x,ls[y]}; ls[y]=edCnt;
    // printf("%d %d\n", x,y);
}

void color(int now,int id) {
    if (!flag) return ;
    vis[now]=1; col[now]=id; scc[now]=scc[0];
    for (int i=ls[now];i;i=e[i].next) {
        if (!vis[e[i].y]) color(e[i].y,id^1);
        else if (col[e[i].y]==id) {
            flag=false;
            return ;
        }
    }
}

void build(int n) {
    rep(i,1,n) {
        rep(j,i+1,n) {
            if (!rc[i][j]||!rc[j][i]) {
                addEdge(i,j);
            }
        }
    }
}

void dp(int n) {
    f[0][0][0]=1;
    rep(k,1,scc[0]) {
        drp(i,n,0) {
            drp(j,n,0) {
                if (i==0&&j==0) continue;
                if (i>=a[k]&&j>=b[k]&&f[k-1][i-a[k]][j-b[k]]) {
                    f[k][i][j]=1;
                    rec[k][i][j]=1;
                }
                if (i>=b[k]&&j>=a[k]&&f[k-1][i-b[k]][j-a[k]]) {
                    f[k][i][j]=1;
                    rec[k][i][j]=2;
                }
            }
        }
    }
}

int main(void) {
    freopen("data.in","r",stdin);
    int n; scanf("%d",&n);
    rep(i,1,n) {
        int x; scanf("%d",&x);
        while (x) {
            rc[i][x]=1;
            scanf("%d",&x);
        }
    }
    build(n);
    flag=true;
    rep(i,1,n) {
        if (!vis[i]) {
            scc[0]++;
            color(i,1);
        }
        if (!flag) {
            puts("-1");
            return 0;
        }
    }
    rep(i,1,n) {
        a[scc[i]]+=(col[i]==1);
        b[scc[i]]+=(col[i]==0);
    }
    dp(n);
    // puts("");
    // rep(i,1,scc[0]) printf("%d %d\n", a[i],b[i]);
    // puts("");
    // printf("%d %d\n", rec[2][2][3], rec[1][1][3]);
    drp(i,n/2,1) {
        int ti=i,tj=n-i;
        if (f[scc[0]][ti][tj]) {
            printf("%d ", ti);
            drp(k,scc[0],1) {
                rep(j,1,n) {
                    if (scc[j]==k&&col[j]+1!=rec[k][ti][tj]) printf("%d ", j);
                }
                if (rec[k][ti][tj]==1) {
                    ti-=a[k];
                    tj-=b[k];
                } else if (rec[k][ti][tj]==2) {
                    ti-=b[k];
                    tj-=a[k];
                }
            }
            ti=i,tj=n-i;
            printf("\n%d ", tj);
            drp(k,scc[0],1) {
                rep(j,1,n) {
                    if (scc[j]==k&&col[j]+1==rec[k][ti][tj]) printf("%d ", j);
                }
                if (rec[k][ti][tj]==1) {
                    ti-=a[k];
                    tj-=b[k];
                } else if (rec[k][ti][tj]==2) {
                    ti-=b[k];
                    tj-=a[k];
                }
            }
            return 0;
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值