P8425 笔记

有点离谱的一个题。

题目大意

有点复杂不写了。

思路

首先找一下区间 \([L, R]\) 合法的充要条件,如果不合法,最小值和最大值都不是 \(P_L, P_R\),于是合法当且仅当 \(P_L\)\(P_R\) 是区间最值。

考虑求解答案的过程,先从 \([1, N]\) 开始,若最小最大值都不在 \(1, N\),就要放一个到 \(P_1\)\(P_N\),从而递归到 \([2, N]\)\([1, N - 1]\)...

于是可以区间 DP,设 \(dp(l, r, x)\) 表示区间 \([l, r]\),值域是 \([x, x + (r - l)]\) 的答案,DP 时如果 \(P_l\)\(P_r\) 是最小/最大就能节省 \(1\) 的步数,总时间复杂度 \(O(N^3)\)

优化上述 DP 是没有前途的,因为状态数已经是 \(O(N^3)\) 了,至少我们需要一个状态数为 \(O(N^2)\) 的算法。

对于上面 DP 的转移,假如把 \(dp(l, r, x)\) 看作一个 \(x \in [l, r], y \in [x, x + r - l]\) 的正方形,转移相当于选择正方形的一个角,并把该角的两条边往里“缩”;再把 \((i, P_i)\)\(N\) 个点画在坐标轴上,则转移的代价为角上是否存在点。

所以我们的目标就是在正方形缩减的过程中,使角上出现的点最多。

于是就可以优化状态了,设 \(dp(i, j, 0/1/2/3)\) 表示点 \((i, P_i)\) 在长度为 \(j\) 的正方形的某个角(通过第 3 维记),能最多选几个点。状态数 \(O(N^2)\),转移 \(O(N)\)\(O(\log N)\),因为可以用二维偏序优化。

看看还能优化什么,突然发觉题目还有一个性质没用到:序列是随机的!这启示我们答案大约是 \(O(\sqrt N)\) 级别的并且还能写妙妙乱搞了

交换定义域和值域:\(dp(i, j, 0/1/2/3)\) 表示点 \((i, P_i)\) 在正方形的某个角,选了 \(j\) 个点,边长最短是多少,转移同样可以二维偏序。这样总时间复杂度就变成了 \(O(N \sqrt{N} \log N)\),可以通过。

但这题还并未结束,因为存在一种乱搞的解法:首先在最外层枚举 \(j\),接着在循环内转移,而每次选出边长最短的 \(500\) 个状态转移就行了,这样甚至可以写 \(O(N)\) 转移。不会证,但数据随机,就当它是对的了!(因为你也没法卡。。。)

代码异常好写(写的 push 没写 pull,状态定义略有出处):

#include <bits/stdc++.h>

using i64 = long long;
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int N;
	cin >> N;

	vector<int> P(N);
	vector<array<int, 4>> dp(N);

	for (int i = 0; i < N; ++i) {
		cin >> P[i];
		--P[i];

		dp[i][0] = min(N - i - 1, N - P[i] - 1);
		dp[i][1] = min(i, N - P[i] - 1);
		dp[i][2] = min(i, P[i]);
		dp[i][3] = min(N - i - 1, P[i]);
	}

	int ans = 0;

	while (true) {
		vector<array<int, 3>> V;
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < 4; ++j) {
				if (~dp[i][j]) {
					V.push_back({i, j, dp[i][j]});
				}
			}
		}
		if (V.empty()) {
			break;
		}
		++ans;

		sort(V.begin(), V.end(), [&](const auto &x, const auto &y) {
			return x[2] > y[2];
		});
		int t = min<int>(V.size(), 500);

		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < 4; ++j) {
				dp[i][j] = -1;
			}
		}

		for (int _ = 0; _ < t; ++_) {
			auto [i, j, v] = V[_];
			int l, r, x, y;

			if (j < 2) {
				x = P[i] + 1;
				y = P[i] + v;
			} else {
				x = P[i] - v;
				y = P[i] - 1;
			}

			if (j == 1 || j == 2) {
				l = i - v;
				r = i - 1;
			} else {
				l = i + 1;
				r = i + v;
			}

			for (int k = l; k <= r; ++k) {
				if (P[k] >= x && P[k] <= y) {
					dp[k][0] = max(dp[k][0], min(r - k, y - P[k]));
					dp[k][1] = max(dp[k][1], min(k - l, y - P[k]));
					dp[k][2] = max(dp[k][2], min(k - l, P[k] - x));
					dp[k][3] = max(dp[k][3], min(r - k, P[k] - x));
				}
			}
		}
	}

	cout << N - ans << "\n";

	return 0;
}

这题确实 push 会好写很多,二维偏序做法需要分讨,以上述写法为例。考虑对 \(dp(k, 0)\) 转移,只需要更新 \(k \ge l \land P_k \ge x\) 的所有 \(k\)\(\min r\)\(\min y\)。其它 \(1, 2, 3\) 也类似处理就行了。

posted @ 2025-08-14 11:29  CTHOOH  阅读(9)  评论(0)    收藏  举报