[SHOI2007]善意的投票
题意:
\(n\) 个小朋友投票睡不睡午觉,每个人都有自己的想法,有 \(m\) 对朋友关系,如果朋友之间投票不一样则为发生冲突,但每个人可以投与自己想法相反的票,但改变想法也算一个冲突,怎样使得冲突最小,输出最小冲突数量。\((2\leq n\leq 300,1\leq m\leq \frac{n(n-1)}2)\)
由数据范围容易联想到网络流做法,但就是不知道怎么建图。
将所有的冲突连成边,比如 \(A\) 想睡觉,\(B\) 不想,那么 \(A->B\) 表示 \(A\) 与 \(B\) 冲突,想要接受这个冲突就要花费 \(1\) 的代价。
如果割掉所有的冲突那么就相当于考虑完所有的代价,所以建完图后跑个最小割。
具体建图的话:
1.将所有想睡觉的连向源点;
2.将所有不想睡觉的连向汇点;
3.将所有朋友关系连上边,这里注意睡觉的向不睡觉的连单向边,意见相同的要互相连双向边。(然而为了方便起见我们可以都连双向边,不会有影响)
网络流建出来的图一般都很玄学,但我们可以举几个简单的例子来思考每条边的意义:
如上图,\(1\) 想睡觉,\(2\) 和 \(3\) 不想,如果意见都保持不变相当于割掉 \(1\) 连向 \(23\) 的边,代价为 \(2\)。但是如果我们只让 \(1\) 改变为不想睡觉,相当于割掉 \(s\) 连向 \(1\) 的边,代价更小为 \(1\)。所以,割掉睡觉向不睡觉的边相当于保持冲突,割掉 \(s\) 连向睡觉的边或不睡觉连向 \(t\) 的边相当于改变想法。
如上图,如果像刚才那样改变 \(1\) 的想法,就是割掉了 \(s\) 连 \(1\) 的边,但是通过 \(4\) 和 \(5\) 同样可以和 \(23\) 流,因为改变了 \(1\) 的想法后与 \(4\) 和 \(5\) 又产生了新的冲突,我们需要继续割掉 \(s\) 连向 \(4\) 和 \(5\) 的边,就是将 \(145\) 的想法都改变,显然不如让 \(1\) 和 \(23\) 冲突更优。所以,相同想法的点建边是为了保证具有相同的想法,如果有一边改变的话这条边也需要被割掉。
理解的差不多,剩下的就是板子了 \(qwq\):
#include <bits/stdc++.h>
using namespace std;
const int N=301010;
const int inf=0x3f3f3f3f;
int n,m,s,t,cnt=1;
int a[N];
struct E{ int to,nxt,cap; } e[N];
int head[N],cur[N];
int dep[N],vis[N];
queue <int> q;
inline int read() {
int sum = 0, f = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') f = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * f;
}
inline void add(int u,int v,int w) {
e[++cnt] = (E){ v,head[u],w }; head[u] = cnt;
e[++cnt] = (E){ u,head[v],0 }; head[v] = cnt;
}
bool SPFA() {
memset(dep,0x3f,sizeof(dep));
memset(vis,0,sizeof(vis));
for(int i=s;i<=t;i++) cur[i] = head[i];
int now = dep[1]; dep[s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop(); vis[u] = 0;
for(int i=head[u]; i; i=e[i].nxt) {
int v = e[i].to;
if(e[i].cap && dep[v] > dep[u]+1) {
dep[v] = dep[u]+1;
if(vis[v]) continue;
q.push(v); vis[v] = 1;
}
}
}
return now != dep[t];
}
int DFS(int u,int flow) {
if(u==t || !flow) return flow;
int dinic = 0, f;
for(int i=cur[u]; i; i=e[i].nxt) {
cur[u] = i;
int v = e[i].to;
if(e[i].cap && dep[v]==dep[u]+1) {
f = DFS( v, min(flow-dinic,e[i].cap) );
if(f) {
dinic += f;
e[i].cap -= f;
e[i^1].cap += f;
if(dinic==flow) break;
}
}
}
return dinic;
}
int koala() {
int dalao = 0;
while( SPFA() )
dalao += DFS(s,inf);
return dalao;
}
int main() {
int x,y;
n = read(); m = read();
s = 0; t = n+1;
for(int i=1;i<=n;i++) {
a[i] = read();
if(a[i]) add(s,i,1); else add(i,t,1);
}
while(m--) {
x = read(); y = read();
add(x,y,1); add(y,x,1);
}
cout<<koala();
return 0;
}