首页 > 其他分享 >洛谷 P8490 [IOI2022] 鲶鱼塘

洛谷 P8490 [IOI2022] 鲶鱼塘

时间:2023-07-22 19:44:48浏览次数:44  
标签:pii P8490 洛谷 int ll t2 t1 IOI2022 fst

洛谷传送门

LOJ 传送门

不算很难的题,但是调起来比较恶心。

下文默认下标从 \(1\) 开始。

设第 \(i\) 列长堤的高度为 \(h_i\)。考虑观察一些性质。

Observation 1:若 \(h_{i - 1} < h_i > h_{i + 1}\),那么 \(h_i = n\) 一定不劣。

若 \(h_i < n\),\([h_i + 1, n]\) 的鱼是抓不到的,并且增大 \(h_i\) 还有机会让旁边两列抓到更多的鱼,所以还不如 \(h_i = n\)。

Observation 2:若 \(h_{i - 1} > h_i < h_{i + 1}\),那么 \(h_i = 0\) 一定不劣。

若 \(h_i > 0\),\([1, h_i]\) 的鱼是抓不到的,让 \(h_i = 0\) 就能抓到 \([1, h_i]\) 的鱼。

Observation 3:对于一组连续的长堤,其一定是单峰的,并且其中 \(\max h_i = n, \min h_i = 0\)。

由上面两个观察易得。

Observation 4:\((i, h_i + 1)\) 一定有鱼。

如果 \((i, h_i + 1)\) 没有鱼,那我们能够增大 \(h_i\) 直到 \((i, h_i + 1)\) 有鱼,这样不会使第 \(i\) 列抓到更少的鱼,而且还有机会使旁边两列抓到更多的鱼。

然后我们就可以开始设计 dp 了。设 \(f_{i, j, 0/1}\) 为 \(h_i = j\),\(h_i\) 处在上升还是下降阶段,能抓到的鱼的价值最大和。这里我们强制规定 \(h_i = n\) 时 \(h_i\) 处于下降阶段,\(h_i = 0\) 时 \(h_i\) 处于上升阶段。

转移大概就是,\(f_{i, j, 0}\) 从 \(\forall k \in [0, j], f_{i - 1, k, 0}\) 转移过来,差分一下加上第 \(i - 1\) 列中行坐标 \(\in [k + 1, j]\) 的鱼的价值和;\(f_{i, j, 1}\) 从 \(\forall k \in [j, n], f_{i - 1, k, 1}\) 转移过来,仍然差分一下加上第 \(i\) 列中行坐标 \(\in [j + 1, k]\) 的鱼的价值和。特别地,\(f_{i, 0, 0}\) 可以从任意 \(f_{i - 1, j, 1}\) 转移过来,\(f_{i, n, 1}\) 可以从任意 \(f_{i - 1, j, 0}\) 转移过来,加上第 \(i - 1\) 列行坐标 \(\in [j + 1, n]\) 的鱼的价值和。

对于价值和的计算,为了方便,我们强制规定,当 \(h_i\) 处于上升阶段时,\(f_i\) 计算 \([1, i - 1]\) 的价值和;\(h_i\) 处于下降阶段时,\(f_i\) 计算 \([1, i]\) 的价值和。但是这样有个问题,就是如果 \(h_{i - 1} > h_i = 0 < h_{i + 1}\) 且 \(h_{i - 1} > h_{i + 1}\),我们的规定会导致第 \(i\) 列行坐标 \(\in [h_{i + 1} + 1, h_{i - 1}]\) 的鱼漏算进答案里面。这种情况我们直接从 \(f_{i - 2}\) 转移过来即可。

本质不同状态数只有 \(O(n + m)\) 种,因此使用 unordered_map 存储状态即可。

使用树状数组分别计算后缀和、前缀 \(\max\)、后缀 \(\max\)(记得每次转移完要清空),时间复杂度 \(O((n + m) \log n)\)。

code
// Problem: P8490 [IOI2022] 鲶鱼塘
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P8490
// Memory Limit: 2 MB
// Time Limit: 500000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = 300100;
const ll inf = 0x3f3f3f3f3f3f3f3fLL;

ll n, m;
unordered_map<ll, ll> f[maxn][2];
vector<pii> vc[maxn];

struct BIT {
	ll c[maxn];
	
	inline void update(ll x, ll d) {
		for (int i = x; i; i -= (i & (-i))) {
			c[i] += d;
		}
	}
	
	inline ll query(int x) {
		ll res = 0;
		for (int i = x; i <= n; i += (i & (-i))) {
			res += c[i];
		}
		return res;
	}
	
	inline void clear(int x) {
		for (int i = x; i; i -= (i & (-i))) {
			c[i] = 0;
		}
	}
} t1;

struct mxBIT {
	ll c[maxn], d[maxn];
	
	inline void init() {
		for (int i = 0; i < maxn; ++i) {
			c[i] = d[i] = -inf;
		}
	}
	
	inline void clear(int x) {
		for (int i = (++x); i <= n + 1; i += (i & (-i))) {
			c[i] = -inf;
		}
		for (int i = x; i; i -= (i & (-i))) {
			d[i] = -inf;
		}
	}
	
	inline void update(int x, ll y) {
		for (int i = (++x); i <= n + 1; i += (i & (-i))) {
			c[i] = max(c[i], y);
		}
		for (int i = x; i; i -= (i & (-i))) {
			d[i] = max(d[i], y);
		}
	}
	
	inline ll query1(int x) {
		ll res = -inf;
		for (int i = (++x); i; i -= (i & (-i))) {
			res = max(res, c[i]);
		}
		return res;
	}
	
	inline ll query2(int x) {
		ll res = -inf;
		for (int i = (++x); i <= n + 1; i += (i & (-i))) {
			res = max(res, d[i]);
		}
		return res;
	}
} t2;

ll max_weights(int _n, int _m, vector<int> _x, vector<int> _y, vector<int> _w) {
	n = _n;
	m = _m;
	for (int i = 0; i < m; ++i) {
		int x = _x[i] + 1, y = _y[i] + 1, w = _w[i];
		vc[x].pb(y, w);
	}
	f[0][0][0] = 0;
	t2.init();
	ll ans = 0;
	for (int i = 1; i <= n; ++i) {
		for (pii p : vc[i - 1]) {
			t1.update(p.fst, p.scd);
		}
		vector<int> S;
		for (pii p : f[i - 1][0]) {
			if (p.scd < 0) {
				continue;
			}
			S.pb(p.fst);
			t2.update(p.fst, p.scd + t1.query(p.fst + 1));
		}
		for (pii p : vc[i]) {
			if (p.fst == 1) {
				continue;
			}
			int x = p.fst - 1;
			f[i][0][x] = t2.query1(x) - t1.query(x + 1);
		}
		f[i][0][0] = t2.query1(0) - t1.query(1);
		f[i][1][n] = t2.query1(n);
		for (pii p : vc[i - 1]) {
			t1.clear(p.fst);
		}
		for (int x : S) {
			t2.clear(x);
		}
		vector<int>().swap(S);
		if (i > 2) {
			for (pii p : vc[i - 1]) {
				t1.update(p.fst, p.scd);
			}
			for (pii p : f[i - 2][1]) {
				if (p.scd < 0) {
					continue;
				}
				S.pb(p.fst);
				t2.update(p.fst, p.scd + t1.query(1) - t1.query(p.fst + 1));
			}
			for (pii p : vc[i]) {
				if (p.fst == 1) {
					continue;
				}
				int x = p.fst - 1;
				f[i][0][x] = max(f[i][0][x], t2.query2(x));
			}
			f[i][0][0] = max(f[i][0][0], t2.query1(n));
			for (pii p : vc[i - 1]) {
				t1.clear(p.fst);
			}
			for (int x : S) {
				t2.clear(x);
			}
			vector<int>().swap(S);
		}
		for (pii p : vc[i]) {
			t1.update(p.fst, p.scd);
		}
		ll mx = 0;
		for (pii p : f[i - 1][1]) {
			if (p.scd < 0) {
				continue;
			}
			mx = max(mx, p.scd);
			t2.update(p.fst, p.scd - t1.query(p.fst + 1));
			S.pb(p.fst);
		}
		for (pii p : vc[i]) {
			if (p.fst == 1) {
				continue;
			}
			int x = p.fst - 1;
			f[i][1][x] = t2.query2(x) + t1.query(x + 1);
		}
		if (i == n) {
			ans = max(ans, t2.query1(n) + t1.query(1));
		}
		f[i][0][0] = max(f[i][0][0], mx);
		f[i][1][n] = max(f[i][1][n], t2.query2(n));
		for (pii p : vc[i]) {
			t1.clear(p.fst);
		}
		for (int x : S) {
			t2.clear(x);
		}
	}
	for (int i = 0; i <= 1; ++i) {
		for (pii p : f[n][i]) {
			ans = max(ans, p.scd);
		}
	}
	return ans;
}

// int main() {
	// int n, m;
	// vector<int> a, b, c;
	// scanf("%d%d", &n, &m);
	// for (int i = 0, x, y, z; i < m; ++i) {
		// scanf("%d%d%d", &x, &y, &z);
		// a.pb(x);
		// b.pb(y);
		// c.pb(z);
	// }
	// printf("%lld\n", max_weights(n, m, a, b, c));
	// return 0;
// }

标签:pii,P8490,洛谷,int,ll,t2,t1,IOI2022,fst
From: https://www.cnblogs.com/zltzlt-blog/p/17574088.html

相关文章

  • 洛谷 P8861 - 线段
    牛逼题。先考虑\(l\le10^5,10^5+1\ler\)的部分分:一种方法是线段树,即因为左右端点是独立的,因此对左右端点各维护一个权值线段树表示有多少个区间以这个值为左/右端点,这样对于修改,左端点的部分相当于先查询\(\lel\)的数的个数,然后将它们都挂到\(l\)上,最后把\(<l\)的部......
  • 洛谷 P9139 [THUPC 2023 初赛] - 喵了个喵 II
    考虑如果每个数恰好出现两次,那么容易得出一个序列合法当且仅当将每个数两次出现位置看作一个区间\([l_i,r_i]\)的两个端点,那么这些区间两两之间不存在包含关系。考虑每个数出现四次的情况,我们钦定两次为\(i\),两次为\(i+n\),这样可以转化为\(2n\)的情况,而容易发现只有\(1122......
  • 再见,洛谷博客!——下载洛谷博客文章
    postedon2022-11-0610:58:17|under学术|source前置知识单组询问F12//copythecodeofblogsfetch('/api/blog/detail/'+BlogGlobals.blogID).then(res=>res.json()).then(res=>console.log(res.data.Content))多次询问下载markdown#fetcher.pyimpo......
  • 洛谷 P9020 - [USACO23JAN] Mana Collection P
    显然,每个法力池最终能收集到的法力只与这个法力池最终被收集到的时间有关。对于一组询问\((s,e)\),假设我们经过了\(k\)个法力池,我们钦定最终被收集到的时间从后到前分别是\(e=a_1,a_2,\cdots,a_k\),那么最大法力值为\(\sum\limits_{i=1}^kc_{a_i}·\sum\limits_{j=2}^i(s-dis......
  • 洛谷 P1122 最大子树和 题解
    一道入门的树形DP。首先我们对于数据进行有序化处理,这便于我们利用数据结构特点(可排序性)来发觉数据性质(有序、单调、子问题等等性质),以便于后续的转化、推理和处理。有序化可以“转化和创造”性质首先将视角从无根树切换为有根树,这样我们就可以得到一个带有最优子结构、无后效性......
  • 洛谷 P2458 [SDOI2006] 保安站岗 - 树形DP
    P2458保安站岗思路:树形DP三个状态:dp[i][0]:节点i位置放保安的最小花费dp[i][1]:节点i位置不放保安,但被子节点的保安看守dp[i][2]:节点i位置不放保安,但被父节点的保安看守状态转移:dp[i][0]:节点i位置放保安,那么它可以合并子节点任何状态的最小值;dp[i][1]:节点i位......
  • 洛谷 P8923 -『MdOI R5』Many Minimizations
    怎么ARC还能撞题的?只能说Kubic牛逼。首先显然没法保序回归。考虑用类似于凸壳优化DP的做法解决原问题(也就是P4331):设\(dp_{i,j}\)表示考虑前\(i\)位,\(x_i=j\)的最小代价,显然有\(dp_{i,j}=\min_{k\lej}\{dp_{i-1,k}+|j-a_i|\}\)\(dp\)值显然是一个折线,用堆维护斜......
  • 洛谷-P9455 题解
    写在前面:本题蒟蒻给出两种做法,一种乱搞贪心(只是目前能过,若能被Hack请和我说),一种正解二分。正文1最坏时间复杂度:\(\mathcal{O}(n+\logV)(V=10^9)\)这个做法是很简单的,在此不多讲。只需要二分超频电压mid即可,若当前mid可行,则令右边界缩小至mid,否则令左边界扩大至mid。......
  • 洛谷 Luogu P1038 [NOIP2003 提高组] 神经网络
    这题看着很吓人实则很简单。求输出层,正着求很麻烦,因为知不道谁连向这个点,所以可以反向建边,反着求。拓扑+dfs,时间复杂度\(\text{O(n+m)}\)#include<iostream>#include<cstdio>#include<queue>#defineN105#defineM(N*N/2+114)structE{intv,w;......
  • 洛谷P1219 [USACO1.5] 八皇后 Checker Challenge
    写在前面我又回来了!偷了几天懒,还认识我吗?甭管认识不认识,还是要自我介绍一番:本人是初学c++的初中生,还是个蒟蒻,最要命的是没有脑子。今天,还请允许我浪费您一点时间,叨叨上几句。本题目来自于洛谷,网址https://www.luogu.com.cn/problem/P1219,建议在洛谷上看一下。本题解非盈利,无恶......