每日一题 2019/4/2

今天学最大权闭合子图

 

如果点权都是正的,答案就是所有的权值之和了,所以要解决的就是有负权的情况

首先建模,一个超级源点和一个超级汇点,源点和正权点连边,汇点和负权点连边,权值就是点值,图中的所有关系边边权为inf,结论就是最大权闭合子图的权值为 所有正点权之和减去最小割的权值和

对这个结论的证明:(该部分摘自https://www.cnblogs.com/TreeDream/p/5942354.html#_label0

1. 最小割一定是简单割

简单割指得是:割(S,T)中每一条割边都与s或者t关联,这样的割叫做简单割。

因为在图中将所有与s相连的点放入割集就可以得到一个割,且这个割不为正无穷。而最小割一定小于等于这个割,所以最小割一定不包含无穷大的边。因此最小割一定一个简单割。

2. 简单割一定和一个闭合子图对应

闭合子图V和源点s构成S集,其余点和汇点t构成T集。

首先证明闭合子图是简单割:若闭合子图对应的割(S,T)不是简单割,则存在一条边(u,v),u∈S,v∈T,且c(u,v)=∞。说明u的后续节点v不在S中,产生矛盾。

接着证明简单割是闭合子图:对于V中任意一个点u,u∈S。u的任意一条出边c(u,v)=∞,不会在简单割的割边集中,因此v不属于T,v∈S。所以V的所有点均在S中,因此S-s是闭合子图。

 

由上面两个引理可以知道,最小割也对应了一个闭合子图,接下来证明最小割就是最大权的闭合子图。

首先有割的容量C(S,T)=T中所有正权点的权值之和+S中所有负权点的权值绝对值之和。

闭合子图的权值W=S中所有正权点的权值之和-S中所有负权点的权值绝对值之和。

则有C(S,T)+W=T中所有正权点的权值之和+S中所有正权点的权值之和=所有正权点的权值之和。

所以W=所有正权点的权值之和-C(S,T)

由于所有正权点的权值之和是一个定值,那么割的容量越小,W也就越大。因此当C(S,T)取最小割时,W也就达到了最大权。

 

然后就是建模,跑最大流了(最大流==最小割)

 

题目:#1398 : 网络流五·最大权闭合子图

//My Conquest Is the Sea of Stars.
#pragma GCC diagnostic error "-std=c++11"
#include<set>
#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<ext/rope>
#include<iostream>
#include<algorithm>
#define endl "\n"
#define fi first
#define se second
#define gcd __gcd
#define pb push_back
#define mp make_pair
#define lowbit(x) x & (-x)
#define PII  pair<int, int> 
#define all(x) x.begin(), x.end()
#define rep(i, a, b) for(__typeof(b) i = a; i <= (b); i++)
#define Rep(i, a, b) for(__typeof(a) i = a; i >= (b); i--)
#define FAST ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int mod = (int)1e9 + 7;
const int maxn = (int)405;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
using __gnu_cxx::crope;

inline int read(){
	char ch = getchar();
	int x = 0, f = 1;
	while(ch < '0' || ch > '9'){
		if(ch == '-') f = -1;
		ch = getchar();
	}
	while('0' <= ch && ch <= '9'){
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * f;
}

struct edge{
	int to, cap, rev;
};

vector<edge> G[maxn];
bool vis[maxn];
int n, m;

void add_edge(int from, int to, int cap){
	G[from].pb((edge){to, cap, G[to].size()});
	G[to].pb((edge){from, 0, G[from].size() - 1});
}

int dfs(int v, int t, int f){
	if(v == t) return f;
	vis[v] = 1;
	rep(i, 0, G[v].size() - 1){
		edge &e = G[v][i];
		if(!vis[e.to] && e.cap > 0){
			int d = dfs(e.to, t, min(f, e.cap));
			if(d > 0){
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}

int max_flow(int s, int t){
	int flow = 0;
	while(1){
		memset(vis, 0, sizeof(vis));
		int f = dfs(s, t, inf);
		if(f == 0) return flow;
		flow += f;
	}
}

int main()
{
	n = read(), m = read();
	int s = 0, t = n + m + 1;
	rep(i, 1, m) {
		int val = read();
		add_edge(n + i, t, val);
	}
	int sum = 0;
	rep(i, 1, n){
		int a = read(), k = read();
		sum += a;
		add_edge(s, i, a);
		rep(j, 1, k){
			int v = read();
			add_edge(i, v + n, inf);
		}
	}
	printf("%d\n", sum - max_flow(s, t));
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值