[51nod1551]集合交易

Description

给出n个集合,每个集合有代价,可正可负。
现在让你选出一些集合,使得这些集合的交集大小等于选出集合的数量
保证所有集合中任意k个集合的并集大于等于k
求最小代价
n<=300,ai<=n

Solution

让我们来看看这个猎奇条件有什么用?

hall定理:二分图有完备匹配的充要条件是X中的任意k个点都至少和Y中的k个点相邻

于是这告诉我们把每个集合和数字看做一个点,从集合点向它所包含的数字连边,所得的二分图必定有完备匹配
这样又有什么用呢?
我们可以把每个集合对应的匹配点钦定为它的代表点,这样选择这个集合就相当于选择了这个代表点
现在我们就只需要考虑数字点,要保证选出的代表点数量=选出的点的数量
这样就可以建立一些依赖关系了,对原图跑最大权闭合子图就是答案

Solution

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
using namespace std;

const int N=305,inf=0x7fffffff;

int n,cnt,x,S,T,a[N][N];
int last[N],next[N*N*2],t[N*N*2],f[N*N*2],l;
void add(int x,int y,int z) {
    t[++l]=y;f[l]=z;next[l]=last[x];last[x]=l;
    t[++l]=x;f[l]=0;next[l]=last[y];last[y]=l;
}

int to[N];
bool bz[N];
bool find(int x) {
    fo(i,1,a[x][0])
        if (!bz[a[x][i]]) {
            bz[a[x][i]]=1;
            if (!to[a[x][i]]||find(to[a[x][i]])) {
                to[a[x][i]]=x;
                return 1;
            }
        }
    return 0;
}

int d[N],dis[N];
bool bfs() {
    memset(dis,0,sizeof(dis));dis[S]=1;
    int i=0,j=1;d[1]=S;
    while (i<j) 
        rep(k,d[++i]) 
            if (f[k]&&!dis[t[k]]) 
                dis[t[k]]=dis[d[i]]+1,d[++j]=t[k];
    return dis[T];
}
int dinic(int x,int y) {
    if (x==T) return y;
    int now=0;
    rep(i,x)
        if (f[i]&&dis[t[i]]==dis[x]+1) {
            int k=dinic(t[i],min(y,f[i]));
            f[i]-=k;f[i^1]+=k;y-=k;now+=k;
            if (!y) break;
        }
    if (!now) dis[x]=-1;
    return now;
}

int main() {
    scanf("%d",&n);S=0;T=n+1;l=1;
    fo(i,1,n) {
        scanf("%d",&a[i][0]);
        fo(j,1,a[i][0]) scanf("%d",&a[i][j]);
    }

    fo(i,1,n) {
        memset(bz,0,sizeof(bz));
        find(i);
    }

    fo(i,1,n) 
        fo(j,1,a[i][0]) 
            if (to[a[i][j]]!=i)
                add(i,to[a[i][j]],inf);

    int ans=0;
    fo(i,1,n) {
        scanf("%d",&x);x=-x;
        if (x>0) add(S,i,x),ans+=x;
        else add(i,T,-x);
    }
    while (bfs()) ans-=dinic(S,inf);
    ans=-ans;
    printf("%d\n",min(ans,0));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值