今天学最大权闭合子图
如果点权都是正的,答案就是所有的权值之和了,所以要解决的就是有负权的情况
首先建模,一个超级源点和一个超级汇点,源点和正权点连边,汇点和负权点连边,权值就是点值,图中的所有关系边边权为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也就达到了最大权。
然后就是建模,跑最大流了(最大流==最小割)
//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;
}