洛谷 P2057 [SHOI2007]善意的投票
考察网络流建图,从最小割的角度考虑比较容易
改写一下题意:
一张图,每个点初始有0/1两个值中的一个,现在让你重新赋值,变化其中一些点的值(还是0/1),使得新图里 相邻两点值不同的对数 加上 新图里点值与原图不同的点数量 的和最小
相当于我们原来有一张图,里面的点分属两个集合,先改变\(x\)个点,使它们所处集合变化,得到新的集合划分,此时原图有\(y\)条边的端点分属不同集合
求最小\(x+y\)
这样转化一下,题目就有了网络流的雏形
先看\(y\),很明显像最小割,我们只要让两个集合分别包含源点/汇点,端点分属两集合的边就是最小割里割去的边
设流量为1,\(y\)就是最小割
然后看\(x\),假设值为0属于源点集合,值为1属于汇点集合
对于值为0的点,跟源点连容量为1的边,跟汇点连容量为0的边(等于不连边),
这样,我们把该点划分到源点集合,割掉与汇点相连的边,代价是0,反过来代价是1,符合题意
其余同理
这样我们就建好了图,跑网络流即可
[code]
#include <bits/stdc++.h>
using namespace std;
int read(){
int x=0,flag=1; char c;
for(c=getchar();!isdigit(c);c=getchar()) if(c=='-') flag=-1;
for(;isdigit(c);c=getchar()) x=((x+(x<<2))<<1)+(c^48);
return x*flag;
}
const int N=350;
int S,T,n,m;
struct Edge{
int to,next,v;
}edge[N*N*2];
int head[N];
int tmp=-1;
void add_edge(int x,int y,int z){
++tmp;
edge[tmp].to=y; edge[tmp].next=head[x]; edge[tmp].v=z;
head[x]=tmp;
}
void add(int x,int y,int z){
add_edge(x,y,z); add_edge(y,x,0);
}
int dep[N];
int cur[N];
bool bfs(){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(S); dep[S]=0;
while(!q.empty()){
int f=q.front(); q.pop();
for(int i=head[f];i!=-1;i=edge[i].next)
if(edge[i].v){
int t=edge[i].to;
if(dep[t]!=-1) continue;
dep[t]=dep[f]+1;
q.push(t);
}
}
return dep[T]!=-1;
}
int dfs(int nod,int val){
if(!val||nod==T) return val;
int flow=0;
for(int i=head[nod];i!=-1;i=edge[i].next)
if(edge[i].v){
int t=edge[i].to;
if(dep[t]!=dep[nod]+1) continue;
int x=dfs(t,min(val,edge[i].v));
val-=x; edge[i].v-=x;
edge[i^1].v+=x; flow+=x;
if(!val) break;
}
return flow;
}
int Dinic(){
int ret=0;
while(bfs()){
memcpy(cur,head,sizeof(head));
ret+=dfs(S,99999999);
}
return ret;
}
int main() {
memset(head,-1,sizeof(head));
n=read(),m=read();
S=0; T=n+1;
for(int i=1;i<=n;i++) { int x=read(); if(x==0) add(i,T,1); else add(S,i,1); }
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y,1); add(y,x,1);
}
printf("%d\n",Dinic());
return 0;
}