首页 > 其他分享 >【luogu AGC031E】Snuke the Phantom Thief(网络流)

【luogu AGC031E】Snuke the Phantom Thief(网络流)

时间:2023-01-19 06:01:08浏览次数:51  
标签:Phantom 个点 KK luogu ll Thief go now dis

Snuke the Phantom Thief

题目链接:luogu AGC031E

题目大意

有 n 个特殊点分布在二维平面上,每个点有坐标和价值。
你要选一些点,总价值是你选的点的价值和。
然后有一些约束,横坐标或者纵坐标大于等于或者小于等于某个值的你选了的点的数量不能超过某个数。
要你最大化总价值。

思路

看到范围会感觉可以用费用流做。
不过按着题目的感觉弄,会发现一个问题,就是你可以算出每个方向上的最严限制,然后每个要选的点就要满足多个条件。
但你是很难做到的,因为你不能让一条边要么不流,要么流满。

所以考虑换一下思路,尝试将题目的条件转化再建模。
发现把条件反过来一下,也就是一个限制变成你选的前 \(k\) 个点或者后 \(k\) 个点的横坐标或纵坐标要小于等于或大于等于某个数。
那换句话说,我们尝试从一个区间的点的限制规划到一个点的,就是第 \(k+1\) 个点的横坐标或者纵坐标要大于或者小于某个数。

不过要注意的一点是横纵坐标应该是分开的,因为我们这里的第 k 个点在横纵坐标里面可能并不是同一个点,是按横坐标或者纵坐标排序得到的第 k 个点。
那我们就有建图的想法了,首先我们枚举一个 k 表示我们选多少个点。

然后源点 \(S\) 连到左边的 \(k\) 个点,右边的 \(k\) 个点连到汇点 \(T\),表示选点。
然后你考虑所有的 \(n\) 个点,进行一个拆点,之间的边的费用就是点权。
然后你考虑左边的 \(k\) 个点表示按横坐标排序,右边的 \(k\) 个点表示按纵坐标排序。
那你一个点 \(x\) 可以跟左边或者后边的某个点 \(y\) 连接就是要它处在这个位置的时候可以满足我们的限制。
(这个限制我们可以预处理出来,就是按前面的确立关系,然后前缀和后缀和一下)

然后跑费用流就行。
不过要注意的是它这里是要费用最大,所以把费用改成小 \(inf-v\),然后费用流加的时候就是流量乘(小 \(inf-dis_T\))

代码

#include<queue>
#include<cstdio>
#include<iostream>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

const ll N = 85;
const ll M = 350;
const ll inf = 1e15;
struct Dian {
	ll x, y, op, z;
}a[N], b[M];
struct node {
	ll x, cst, to, nxt, op;
}e[N * M * 2 * 2];
ll n, m, S, T, le[(N + M) * 2], KK;
ll L[N], R[N], U[N], D[N], ans;

void add(ll x, ll y, ll z, ll cst) {
	e[++KK] = (node){z, cst, y, le[x], KK + 1}; le[x] = KK;
	e[++KK] = (node){0, -cst, x, le[y], KK - 1}; le[y] = KK;
}

ll deg[(N + M) * 2], lee[(N + M) * 2];
ll dis[(N + M) * 2];
bool in[(N + M) * 2];
queue <ll> q;

bool SPFA() {
	for (ll i = 1; i <= T; i++) deg[i] = in[i] = 0, dis[i] = INF, lee[i] = le[i];
	while (!q.empty()) q.pop(); ll chk = dis[T];
	q.push(S); dis[S] = 0; deg[S] = 1; in[S] = 1;
	while (!q.empty()) {
		ll now = q.front(); q.pop();
		for (ll i = le[now]; i; i = e[i].nxt)
			if (dis[e[i].to] > dis[now] + e[i].cst && e[i].x) {
				dis[e[i].to] = dis[now] + e[i].cst;
				deg[e[i].to] = deg[now] + 1;
				if (!in[e[i].to]) {
					in[e[i].to] = 1; q.push(e[i].to);
				}
			}
		in[now] = 0;
	}
	return dis[T] != chk;
}

ll dfs(ll now, ll sum) {
	if (now == T) return sum;
	ll go = 0;
	for (ll &i = lee[now]; i; i = e[i].nxt)
		if (e[i].x & dis[e[i].to] == dis[now] + e[i].cst && deg[e[i].to] == deg[now] + 1) {
			ll this_go = dfs(e[i].to, min(sum - go, e[i].x));
			if (this_go) {
				e[i].x -= this_go; e[e[i].op].x += this_go;
				go += this_go; if (go == sum) return go;
			}
		}
	deg[now] = -1; return go;
}

ll Dinic() {
	ll re = 0, cnt = 0;
	while (SPFA()) {
		ll x = dfs(S, INF);
		re += x * (inf - dis[T]);
	}
	return re;
}

ll work(ll k) {
	S = (n + k) * 2 + 1, T = S + 1;
	for (ll i = 1; i <= k; i++) {
		add(S, i, 1, 0); add(i + k, T, 1, 0);
	}
	for (ll i = 1; i <= n; i++) {
		for (ll j = 1; j <= k; j++) {
			if (L[j] <= a[i].x && a[i].x <= R[k - j + 1]) add(j, 2 * k + i, 1, 0);
			if (D[j] <= a[i].y && a[i].y <= U[k - j + 1]) add(2 * k + n + i, j + k, 1, 0);
		}
		add(2 * k + i, 2 * k + n + i, 1, inf - a[i].z);
	}
	
	ll re = Dinic();
	for (ll i = 1; i <= T; i++) le[i] = 0; KK = 0;
	return re;
}

int main() {
	scanf("%lld", &n);
	for (ll i = 1; i <= n; i++) {
		scanf("%lld %lld %lld", &a[i].x, &a[i].y, &a[i].z);
	}
	scanf("%lld", &m);
	for (ll i = 0; i <= n + 1; i++) R[i] = U[i] = inf;
	for (ll i = 1; i <= m; i++) {
		char c = getchar(); while (c != 'L' && c != 'R' && c != 'U' && c != 'D') c = getchar();
		if (c == 'L') b[i].op = 0; if (c == 'R') b[i].op = 1; if (c == 'D') b[i].op = 2; if (c == 'U') b[i].op = 3;
		scanf("%lld %lld", &b[i].x, &b[i].y);
		if (b[i].op == 0) L[b[i].y + 1] = max(L[b[i].y + 1], b[i].x + 1);
		if (b[i].op == 1) R[b[i].y + 1] = min(R[b[i].y + 1], b[i].x - 1);
		if (b[i].op == 2) D[b[i].y + 1] = max(D[b[i].y + 1], b[i].x + 1);
		if (b[i].op == 3) U[b[i].y + 1] = min(U[b[i].y + 1], b[i].x - 1);
	}
	for (ll i = 1; i <= n; i++) L[i] = max(L[i], L[i - 1]), R[i] = min(R[i], R[i - 1]), D[i] = max(D[i], D[i - 1]), U[i] = min(U[i], U[i - 1]);
	//预处理关系
	
	for (ll i = 1; i <= n; i++) ans = max(ans, work(i));
	printf("%lld", ans);
	
	return 0;
}

标签:Phantom,个点,KK,luogu,ll,Thief,go,now,dis
From: https://www.cnblogs.com/Sakura-TJH/p/luogu_AGC031E.html

相关文章

  • luogu P7323 [WC2021] 括号路径
    题面传送门为了方便,我们仅保留\((v,u,-w)\)的反向边。可以发现,如果某个点\(u\)到\(v_1,v_2\)同时有相同边权的边,那么\((v_1,v_2)\)就是一个合法的点对。因此可以这样暴力......
  • Luogu P4793 [AHOI2008] 矩形藏宝地
    链接难度:\(\texttt{省选/NOI-}\)有\(n\)个矩形,左下角为\((x1,y1)\),右上角为\((x2,y2)\),问被其他的矩形包含的矩形有多少个。数据范围:\(1\len\le200000,x1<x2,y1<y......
  • Luogu P4013 数字梯形问题
    说实话这道题挺乐的,去年11月学网络流时碰到这道题,一直没想通,结果碰到考试月,被遣返回家,一个多月没摸了,今天看到这道题一下子想通了,于是想记下来。题目传送门P4013数字梯......
  • Luogu7509 撕裂消除 - 期望dp -
    题目链接:https://www.luogu.com.cn/problem/P7509题解:设\(dp[i][j][0/1]\)表示考虑到第\(i\)个位置,已经形成了极大的\(j\)段,当前位置为0/1的期望值;\(g[i][j][0......
  • 【luogu CF1707D】Partial Virtual Trees(容斥)(DP)
    PartialVirtualTrees题目链接:luoguCF1707D题目大意给你一棵以1为根的数,问你对于每个长度,有多少个点集序列,第一个点集是全部点,最后一个点集只有1号点,且中间每个点......
  • luogu P3518 [POI2011]SEJ-Strongbox | loj #2160. 「POI2011 R2 Day0」保险箱 Strong
    代码已在loj上不开O2通过。下文均在\(Z_n\)下考虑。首先,你考虑选出一些数,能组成的数。即ttps://www.cnblogs.com/xugangfan/p/17040634.html那么对于一个不在群......
  • Luogu P5465 [PKUSC2018] 星际穿越
    观察可以发现一个结论,可以视作每个点\(i\)可以一步到达\(l_i\simn\)的每一个点。发现对于\(a<b<x\),\(dist(a,x)\gedist(b,x)\)第一步是相当特殊的,因为第一步......
  • luogu P5291 [十二省联考 2019] 希望
    题面传送门真的很想吐。题目的意思大概就是在一棵树上选出\(k\)个联通块,使得这\(k\)个联通块有交。显然联通块的交还是联通块,因此转化为对联通块计数。而联通块个数等于......
  • [luogu]P2123 皇后游戏
    题目传送门分析和国王游戏一样的思路直接考虑邻项交换观察易知排在后面的大臣获得的奖赏一定更多假设前\(i-1\)位左手上的数和为\(a\),第\(i-1\)位获得奖赏为\(......
  • luogu P1971 [NOI2011] 兔兔与蛋蛋游戏
    题面传送门没想到二分图博弈这么早就考了?首先我们发现,如果将和起点行列之和奇偶性一样的点设为黑色,其余设为白色,那么每次空格只会在异色边之间走,而不会在同色边之间走。......