洛谷OJ:P2057 [SHOI2007]善意的投票 / [JLOI2010]冠军调查(最小割)

题目描述

幼儿园里有 n 个小朋友打算通过投票来决定睡不睡午觉。

对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神。

虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投和自己本来意愿相反的票。

我们定义一次投票的冲突数为好朋友之间发生冲突的总数加上和所有和自己本来意愿发生冲突的人数。

我们的问题就是,每位小朋友应该怎样投票,才能使冲突数最小?

输入格式

第一行两个整数 n,m。其中 n 代表总人数,m 代表好朋友的对数。

第二行 n 个整数,第 i 个整数代表第 i 个小朋友的意愿:当它为 1 时表示同意睡觉,当它为 0 时表示反对睡觉。

接下来 m 行,每行有两个整数 i,j,表示 i,j 是一对好朋友,我们保证任何两对 i,j 不会重复。

输出格式

一行一个整数,即可能的最小冲突数。

输入输出样例

输入 #1复制

3 3
1 0 0
1 2
1 3
3 2

输出 #1复制

1

说明/提示

对于 100% 的数据,2≤n≤300,1≤m≤n(n−1)/2。

思路:对网络流熟悉的人相信一眼能看出这是最小割问题,我们将两种意见0和1分别作为源点S和汇点T,而我们的目的是割最小的边使得S和T成为两个不同的集合,我们可以这样想,若两个集合之间存在边相连,这不就相当于还存在着0和1连边,也就是所谓的冲突嘛,想到这里你应该能悟到为什么是最小割了。

对于连边策略:我们将源点与所有初始意愿为1的点连边,汇点与所有初始意愿为0的点连边,对于存在朋友关系的两点,我们将其连为双向边,这里是很容易想的嘛,你可以将两个点都变为1,也可以将两个点都变为0哇。连完边之后直接上dinic板子就好啦。【PS:做多了你就会发现,网络流永远难在建边,剩下的往往是套板子!】

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
#define maxn 550
#define ll long long
#define inf 1e18
int n,m,s,t,lv[maxn],cur[maxn];
ll edges[maxn][maxn];
//lv是每个点的层数
bool bfs(){
	memset(lv,-1,sizeof(lv));
	lv[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int i=1;i<=n;i++){
			if(edges[now][i]>0 && lv[i]==-1){
				lv[i]=lv[now]+1;
				q.push(i);
			}
		}
	}
	return lv[t]!=-1;
}
ll dfs(int p=s,ll flow=inf){
	if(p==t)
		return flow;
	ll mn=flow;
	for(int i=1;i<=n;i++){
		if(edges[p][i]>0 && lv[i]==lv[p]+1){
			ll c=dfs(i,min(edges[p][i],mn));
			mn-=c;
			edges[p][i]-=c;
			edges[i][p]+=c;
		}
	}
	return flow-mn;
}
ll dinic(){
	ll maxFlow=0;
	while(bfs())
		maxFlow+=dfs();
	return maxFlow;
}
int main(void){
	int x,y;
	scanf("%d%d",&n,&m);
	t=n+1;
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		if(x==1) 
			edges[s][i]=1;
		else
			edges[i][t]=1;
	}
	++n;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		edges[x][y]=edges[y][x]=1;
	}
	printf("%lld\n",dinic());
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值