兔子的战役(最小割)

很久很久之前,树林里住着一群兔子。但是不幸的时,森林里还有一匹狼,这匹狼隔三差五就来骚扰兔子,兔子为了抵抗狼的袭击,也组织了军队来与狼战斗。这一系列战役发生在很久之前了,现在的人们只能通过史书来了解当时的战役。
我们假设一共有N只兔子,编号为1-N,史书上记载了K场兔子与狼之间的战役。每场战役,兔子们派出这N只兔子的中的若干只(即一个集合)去与狼战斗,史书上同时也记录了这场战役的结果(兔子胜利或者狼胜利)。但是史书的记录并不可靠,有时会出现矛盾的情况,矛盾的情况有以下两种:

1、有一场战役兔子集合S去迎战狼,并且兔子胜利,但是还有一场战役,兔子集合S’去迎战狼,但是兔子失败了。其中S是S’的子集。(这种情况对应了,已经取得胜利的兔子们再加上了一些兔子却失败了)

2、有一场战役兔子集合S去迎战狼,并且兔子失败,但是还有一场战役,兔子集合S’去迎战狼,但是兔子胜利了。其中S是S’的超集。(这种情况对应了,已经失败的兔子们再减去了一些兔子却胜利了)

你需要修改史书上若干条记录的结果(即兔子胜利改成失败,或者失败改成胜利),使得以上两种矛盾都不会出现。

输入格式:

第一行两个整数N和K,分别表示兔子的个数和史书上记录的个数
接下来K行,每行表示一条记录。每个记录由一个整数h开始,表示这场战役由h个兔子参战,在接下来h个整数,表示参与这场战役的兔子们,保证这h个编号互不相同。再最后一个字母W或者F,W表示这场战役兔子胜利,F表示兔子失败

输出格式:

一行一个整数,表示最小修改的记录条数

数据范围

N , K ≤ 1000 N,K\le 1000 N,K1000

解法

首先根据集合的包含关系连边,注意如果两个集合相同,也要连边。然后我们发现唯一一种冲突情况就是一个大集合输了,一个小集合反而赢了。如果我们将原图中一个胜了的集合的权值设为1,一个输了的集合权值设为0,并且将权值为1的集合与S连边,权值为0的集合向T连边,每条边容量为1,再链接那些有冲突的边,容量设为inf,那么答案就是这张图的最小割。
考虑正确性:一条没有冲突的边对答案实际上没有影响,因为如果这条边的大集合和某一个集合有冲突,那么小集合也一定和那个集合有冲突。如果是小集合有冲突,那对于大集合而言没有影响。

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	char c=getchar();int t=0,f=1;
	while((!isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
	while((isdigit(c))&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,k;
struct node{
	int a[1005],h,st;
}a[1005];
struct edge{
	int v,p,w;
}e[2000005];
int h[1005],cnt=1;
vector<int> g[1005];
inline void add(int a,int b,int c){
	e[++cnt].p=h[a];
	e[cnt].v=b;
	e[cnt].w=c;
	h[a]=cnt;
}
int s,t,dis[1005],ht[1005];
bool bfs(){
	queue<int> q;
	while(!q.empty())q.pop();
	q.push(s);
	memset(dis,-1,sizeof(dis));
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=h[u];i;i=e[i].p){
			int v=e[i].v;
			if(e[i].w&&(dis[v]==-1)){
				dis[v]=dis[u]+1;
			//  printf("%d %d\n",v,dis[v]);
				q.push(v);
				if(v==t)return 1;
			}
		}
	}
	return 0;
}
int dfs(int u,int rest){
	if(u==t||rest==0)return rest;
	int tot=0;
	for(int &i=ht[u];i;i=e[i].p){
		int v=e[i].v;
		if(e[i].w&&(dis[v]==dis[u]+1)){
			int di=dfs(v,min(rest,e[i].w));
			e[i].w-=di;e[i^1].w+=di;
			rest-=di;tot+=di;
			if(rest==0)break;
		}
	}
	return tot;
}
int dinic(){
	int ans=0;
	while(bfs()){
		for(int i=s;i<=t;i++)ht[i]=h[i];
		ans+=dfs(s,inf);
	}
	return ans;
}
signed main(){
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
	n=read(),k=read();
	s=0,t=k+1;
	for(int i=1;i<=k;i++){
		int h=read();
		for(int j=1;j<=h;j++){
			int x=read();
			a[i].a[x]++;
		}
		a[i].h=h;
		char c;
		cin>>c;
		if(c=='W'){
			add(s,i,1);
			add(i,s,0);
			a[i].st=1;
		}
		else {
			add(i,t,1);
			add(t,i,0);
			a[i].st=0;
		}
	}
	for(int i=1;i<=k;i++){
		for(int j=1;j<=k;j++){
			if(a[i].h<a[j].h)continue;
			int flag=1;
			for(int x=1;x<=n;x++){
				if(a[j].a[x]&&(!a[i].a[x])){flag=0;break;}
			}
			if(flag){
				if(a[j].st==1&&a[i].st==0){
				//printf("%d %d\n",i,j);
				add(j,i,inf);
				add(i,j,0);
				}
			}
		}
	}
	printf("%d\n",dinic());
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值