题目大意:
分析:满足第 i 个人必须要建 ai,bi两个中转站。假设存在一个最优解,最优解一定是满足了部分人。从 i 各建一条边到 ai,bi ,可以发现最优解符合最大权闭合子图的定义,得到最优解一定只建需要的中转站,不会多建其它多余的中转站(没有多余消费)
解决最大权闭合子图可以用网络流模型。
撇开最大权闭合子图来说:
可以将所有人连一条边到S,容量为获利,所有中转站连一条边到T,容量为成本,然后每个人 i 各连一条边到 ai和bi,容量为无限。
最小割[S,T] S集合对应一个最优解:证明与闭合子图中证明类似。
由于这种图的割一定是一个简单割,如果割左边代表不满足这个人的要求(要扣去这个人的盈利),割右边代表要建这个中转站,要扣去这个中转站的成本,用总获利和 减去最大流就得到最大盈利,最大流中包含了要建的花费,和没有满足的获利,这些都是要扣掉的。
最大权闭合子图的网络流模型理解:
闭合子图是在一个有向图上的子图,子图满足所有点的出度对应的点都在子图内,也就是在子图
E
′
E'
E′中, 所有的
<
u
,
v
>
∈
E
′
<u,v> \in E'
<u,v>∈E′,都有
u
∈
V
′
,
v
∈
V
′
u \in V' ,v \in V'
u∈V′,v∈V′ 。
每一个点有一个点权,闭合子图的权为子图内所有点的权值之和,最大权闭合子图就是权最大的闭合子图。
网络流模型:从s点连一条边到所有点权为正的点,容量为该点权值;所有点权为负的点连一条边到t点,容量为该点权值的绝对值,原来的边保留,容量为无穷大,这个图的最小割是一个简单割。定义S集合为与 s 点间还有容量的点,T集合为与t点还有容量的点。
最小割将图分成了[S,T],其中S集合(除去s点)对应闭合子图的一个解(证明:若存在一条边<u,v>,u点在S集合而T点在t集,由于<u,v>容量为无限,那么S,T还存在增广路径,与最大流最小割冲突,故S对应一个解)。
这个解的答案为:S集合中除去s,所有正点权之和 减轻 所有负点权的绝对值之和。
最优解答案:所有正点权之和
t
o
t
tot
tot 减去最大流 C
证明:(可以参考《最小割模型在信息学竞赛中的应用》,要定义一些符号,偷懒不写了)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int maxm = 6e5 + 10;
const int inf = 0x3f3f3f3f;
struct ss{
int u,v,w,nxt;
}edg[maxm];
int head[maxn],cnt,d[maxn],n,m,a[maxn];
void init() {
cnt = 0;
memset(head,-1,sizeof head);
}
void add(int u,int v,int w) {
edg[cnt].u = u;
edg[cnt].v = v;
edg[cnt].w = w;
edg[cnt].nxt = head[u];
head[u] = cnt++;
}
bool bfs(int s,int t) {
queue<int> q;
memset(d,0,sizeof d);
q.push(s);d[s] = 1;
while(!q.empty()) {
int top = q.front();
q.pop();
for(int i = head[top]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,w = edg[i].w;
if(w && !d[v]) {
d[v] = d[top] + 1;
q.push(v);
}
}
}
return d[t] > 0;
}
int dfs(int s,int t,int inflow) {
if(s == t || !inflow) return inflow;
int flow = 0;
for(int i = head[s]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,w = edg[i].w;
if(w && d[v] == d[s] + 1) {
int x = dfs(v,t,min(w,inflow));
inflow -= x;flow += x;
edg[i].w -= x;edg[i ^ 1].w += x;
if(!inflow) break;
}
}
if(flow == 0) d[s] = -2;
return flow;
}
int dinic(int s,int t) {
int ans = 0;
while(bfs(s,t)) ans += dfs(s,t,inf);
return ans;
}
int main() {
init();
scanf("%d%d",&n,&m);
int s = 0,t = n + m + 1;
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i]);
add(i,t,a[i]);
add(t,i,0);
}
int sum = 0;
for(int i = 1; i <= m; i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(i + n,x,inf);
add(x,i + n,0);
add(i + n,y,inf);
add(y,i + n,0);
add(s,i + n,z);
add(i + n,s,0);
sum += z;
}
sum -= dinic(s,t);
printf("%d\n",sum);
return 0;
}