最大权闭合子图(最小割模型)

1,定义:

1,最大权闭合子图是最小割的一个模型。即每一个子图中的每一个点,其出边的点也全应该在这个子图中。而所有子图中,其点的权值和最大就是最大权闭合子图。

2,构建该图,我们把所有正权值点与源点s连接(其边权为点权),所有负权值点有汇点t连接(其边权为负点权(即改为正数)),而点与点之间的边都是INF的边。这样,我们的最小割一定割的是连接s或者t的边(最小割不会割INF边的)。

3,我们说过,点的出边一定要与点在同一个子图中,所以建立点与点之间的关系(如a->b,a->c),其关系为走a必须同时走b和c。

4,我们把所有正权值点加起来为sum,而我们走最小割时,就等于舍弃这个正权值边或者走了这个负权值边。

即最大权闭合子图答案为sum-最小割。

证明如图:

例题:Gold Mine

题意(读起来有点坑,翻译一下)

有t个测试,每个测试第一个输入n,表示有n个区域有金矿。

接下来对n个金矿进行描述,首先输入h,表示这个金矿有h个金块。

接下来对h个金块描述,首先是a1,a2,w表示挖这个金块的代价,挖这个金块获得的价值,挖这个金块前必须先挖掉前面w个金块才可以。

接下来对w个金块描述,每行输入x1,x2,表示这个金块在x1行,x2列。

思路:

就是裸题.......

我用的isap算法最大流三大算法——3,ISAP算法

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
#define int ll
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int M = 5e4 + 10;
const int N = 3e3 + 10;

int head[M], gap[N], dep[N], now[M];
int num, ans, s, t, cnt;
struct node
{
	int next, to, w;
} edge[M];

void add(int u, int v, int w)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	edge[num].w = w;
	head[u] = num;

	edge[++num].next = head[v];
	edge[num].to = u;
	edge[num].w = 0;
	head[v] = num;
}

void init()
{
	memset(head, 0, sizeof(head));
	memset(dep, -1, sizeof(head));
	memset(gap, 0, sizeof(gap));
	num = 1;
	ans = cnt = 0;
}

void bfs()
{
	queue<int>q;
	dep[t] = 0;
	gap[0]++;
	q.push(t);
	while (!q.empty())
		{
			int u = q.front();
			q.pop();
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to, w = edge[i ^ 1].w;
					if (dep[v] == -1 && w)
						{
							dep[v] = dep[u] + 1;
							gap[dep[v]]++;
							q.push(v);
						}
				}
		}
}

ll dfs(int u, ll sum)
{
	if (u == t)return sum;
	int k, res = 0;
	for (int i = now[u]; i && sum; i = edge[i].next)
		{
			now[u] = i;
			int v = edge[i].to, w = edge[i].w;
			if (dep[v] == dep[u] - 1 && w)
				{
					k = dfs(v, min(sum, w));
					if (!k)continue;
					edge[i].w -= k;
					edge[i ^ 1].w += k;
					res += k;
					sum -= k;
					if (!sum)return res;
				}
		}
	gap[dep[u]]--;
	if (!gap[dep[u]])dep[s] = cnt + 2;
	dep[u]++;
	gap[dep[u]]++;
	return res;
}

void msolve()
{
	init();
	int n, h, a1, a2, w, sum = 0;
	cin >> n;
	s = 0, t = 25 * n + 1;
	for (int i = 0; i < n; ++i)
		{
			cin >> h;
			for (int j = 1; j <= h; ++j)
				{
					cnt++;
					int u = i * 25 + j;
					cin >> a1 >> a2 >> w;
					int a = a2 - a1;
					if (a > 0)add(s, u, a), sum += a;//正权值点连接s,负权值点连接t
					else add(u, t, -a);
					for (int k = 1; k <= w; ++k)
						{
							cin >> a1 >> a2;
							add(u, (a1 - 1) * 25 + a2, llINF);//点与点之间边是INF
						}
				}
		}
	//isap算法
	bfs();
	while (dep[s] < cnt + 2 && dep[s] != -1)
		{
			memcpy(now, head, sizeof(head));
			ans += dfs(s, llINF);
		}
	cout << sum - ans << endl;
}

int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	for (int i = 1; i <= t; ++i)
		{
			cout << "Case #" << i << ": ";
			msolve();
		}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值