题目
题目链接:https://www.luogu.com.cn/problem/P2057
幼儿园里有 \(n\) 个小朋友打算通过投票来决定睡不睡午觉。
对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神。
虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投和自己本来意愿相反的票。
我们定义一次投票的冲突数为好朋友之间发生冲突的总数加上和所有和自己本来意愿发生冲突的人数。
我们的问题就是,每位小朋友应该怎样投票,才能使冲突数最小?
思路
第一道完全自己想的一次过的网络流题 /fad。
我们从源点 \(S\) 向投 \(1\) 的人连一条流量为 \(1\) 的边,从投 \(0\) 的人向汇点 \(T\) 连一条流量为 \(1\) 的边。
然后对于一对朋友 \(u,v\),如果他们投票相同,那么互相连一条流量为 \(1\) 的边,否则从投 \(1\) 的人向投 \(0\) 的人连一条流量为 \(1\) 的边。
这样我们就保证了如果存在一条路径 \(S\to x\to y\to T\),割 \(x\to y\) 相当于 \(x\) 和 \(y\) 都不改票;割 \(S\to x\) 或 \(y\to T\) 都相当于改变选票,但是改变选票的人的其他好友依然可以有流量流向他,所以还是需要断掉其他边。
此时这张图的最小割就是答案。跑最大流即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=310,M=N*N*4,Inf=1e9;
int n,m,S,T,maxf,tot=1,head[N],a[N],cur[N],dep[N];
struct edge
{
int next,to,flow;
}e[M];
void add(int from,int to,int flow)
{
e[++tot].to=to;
e[tot].flow=flow;
e[tot].next=head[from];
head[from]=tot;
}
bool bfs()
{
memcpy(cur,head,sizeof(cur));
memset(dep,0x3f3f3f3f,sizeof(dep));
queue<int> q; q.push(S);
dep[S]=1;
while (q.size())
{
int u=q.front(); q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (e[i].flow && dep[v]>dep[u]+1)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return dep[T]<Inf;
}
int dfs(int x,int flow)
{
if (x==T)
{
maxf+=flow;
return flow;
}
int used=0,ret;
for (int i=cur[x];~i;i=e[i].next)
{
int v=e[i].to;
if (e[i].flow && dep[v]==dep[x]+1)
{
ret=dfs(v,min(flow-used,e[i].flow));
e[i].flow-=ret; e[i^1].flow+=ret;
used+=ret;
if (used==flow) return flow;
}
}
return used;
}
void dinic()
{
while (bfs())
dfs(S,Inf);
}
int main()
{
S=N-1; T=N-2;
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if (a[i]) add(S,i,1),add(i,S,0);
else add(i,T,1),add(T,i,0);
}
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
if (a[y]==1) swap(x,y);
if (a[x]==a[y]) add(x,y,1),add(y,x,1);
else add(x,y,1),add(y,x,0);
}
dinic();
printf("%d",maxf);
return 0;
}