bzoj1497:最大获利(最大权闭合子图,最小割模型应用)

题目大意:
在这里插入图片描述

分析:满足第 i 个人必须要建 ai,bi两个中转站。假设存在一个最优解,最优解一定是满足了部分人。从 i 各建一条边到 ai,bi ,可以发现最优解符合最大权闭合子图的定义,得到最优解一定只建需要的中转站,不会多建其它多余的中转站(没有多余消费)

解决最大权闭合子图可以用网络流模型。

撇开最大权闭合子图来说:
可以将所有人连一条边到S,容量为获利,所有中转站连一条边到T,容量为成本,然后每个人 i 各连一条边到 ai和bi,容量为无限。

最小割[S,T] S集合对应一个最优解:证明与闭合子图中证明类似。

由于这种图的割一定是一个简单割,如果割左边代表不满足这个人的要求(要扣去这个人的盈利),割右边代表要建这个中转站,要扣去这个中转站的成本,用总获利和 减去最大流就得到最大盈利,最大流中包含了要建的花费,和没有满足的获利,这些都是要扣掉的。

最大权闭合子图的网络流模型理解:
闭合子图是在一个有向图上的子图,子图满足所有点的出度对应的点都在子图内,也就是在子图 E ′ E&#x27; E中, 所有的 &lt; u , v &gt; ∈ E ′ &lt;u,v&gt; \in E&#x27; <u,v>E,都有 u ∈ V ′ , v ∈ V ′ u \in V&#x27; ,v \in V&#x27; uV,vV
每一个点有一个点权,闭合子图的权为子图内所有点的权值之和,最大权闭合子图就是权最大的闭合子图。

网络流模型:从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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值