P9870 题解

发布时间 2023-11-22 13:16:48作者: liangbowen

blog。NOIP2023 T3,特殊性质题。

什么是特殊性质题?就是题目给出了你极其神秘的性质,从而引导你想出正解。

本篇题解将从部分分的角度,一步步讲述部分分与正解的关系。这样看的话,本题会变得十分简单。

\(\text{Task }1\sim7\)\(O(Tnm)\)

首先转换题意。\(\forall(f_i-g_i)(f_j-g_j)>0\),本质上就是满足两者中的一个:

  • 所有 \(1\le i\le 10^{100}\) 都有 \(f_i<g_i\)
  • 所有 \(1\le i\le 10^{100}\) 都有 \(f_i>g_i\)

这两件事情本质相同,我们只考虑第一件事情(\(\forall f_i<g_i\)),第二件事情就是将 \(f,g\) 对调一下再处理。

你想一想拓展的本质。其实就是有两个指针 \(X_i\)\(Y_j\),每次如果有 \(X_i<Y_j\),那么可以做:

  • \((i,j)\to(i+1,j)\),表示 \(f\) 的下一位是 \(X_{i+1}\)\(g\) 的下一位仍然是 \(Y_j\)
  • \((i,j)\to(i,j+1)\),表示 \(f\) 的下一位仍然是 \(X_{i}\)\(g\) 的下一位是 \(Y_{j+1}\)
  • \((i,j)\to(i+1,j+1)\),表示 \(f\) 的下一位是 \(X_{i+1}\)\(g\) 的下一位是 \(Y_{j+1}\)

于是考虑 DP。\(dp_{i,j}\) 表示 \(X\) 匹配到第 \(i\) 位,\(Y\) 匹配到第 \(j\) 位,是否可行。转移即:

\[X_i<Y_j, dp_{i,j}\gets dp_{i-1,j}\cup dp_{i,j-1}\cup dp_{i-1,j-1} \]

答案即 \(dp_{n,m}\)。这一部分的代码如下,时间复杂度 \(O(Tnm)\)

#include <iostream>
#include <cstdio>
#include <cassert>
using namespace std;
const int N = 5e5 + 5;
bool dp[2005][2005];
bool chk(int x[], int y[], int n, int m) //是否可以构造出 fi<gi
{
	if (x[1] <= y[1] || x[n] <= y[m]) return false;
	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) dp[i][j] = false;
	dp[1][1] = true;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (x[i] > y[j]) dp[i][j] |= (dp[i - 1][j - 1] | dp[i - 1][j] | dp[i][j - 1]);
	return dp[n][m];
}
int x[N], y[N], ttx[N], tty[N];
int main()
{
	int n, m, q;
	scanf("%*d%d%d%d", &n, &m, &q);
	for (int i = 1; i <= n; i++) scanf("%d", &x[i]);
	for (int i = 1; i <= m; i++) scanf("%d", &y[i]);
	putchar(chk(x, y, n, m) || chk(y, x, m, n) ? '1' : '0');
	while (q--)
	{
		for (int i = 1; i <= n; i++) ttx[i] = x[i];
		for (int i = 1; i <= m; i++) tty[i] = y[i];
		int cx, cy;
		scanf("%d%d", &cx, &cy);
		while (cx--) {int p, v; scanf("%d%d", &p, &v); ttx[p] = v;}
		while (cy--) {int p, v; scanf("%d%d", &p, &v); tty[p] = v;}
		putchar(chk(ttx, tty, n, m) || chk(tty, ttx, m, n) ? '1' : '0');
	}
	return 0;
}

\(\text{Task }8\sim14\)\(O(T(n+m))\) 特殊性质

考虑上面那个做法是在干啥。

\(A_{i,j}=[X_i<Y_j]\),从 \((1,1)\) 开始,每次可以向右、下、右下的 \(A_{i,j}=1\) 的点走一步,问能否走到 \((n,m)\)

首先,如果 \(X_{\min}\ge Y_{\min}\),说明 \(Y_{\min}\) 的那一列的全部 \(A_{i,j}\) 都是 \(0\)。显然路被堵死了,走不到捏。

同理,\(Y_{\max}\le X_{\max}\) 也是走不到。

考虑完这些小情况后,根据这个特殊性质,必然有:

  • 对于 \(1\le i\le m\),都有 \(A_{n,i}=1\)
  • 对于 \(1\le i\le n\),都有 \(A_{i,m}=1\)

这说明,只要我们能走到第 \((n-1)\) 行或者第 \((m-1)\) 列,就一定能顺着这条通路到达 \((n,m)\)。看起来像下面这样:

以此类推,找到 \(1\sim(n-1)\) 的最小值,如果有 \(X_\min<Y_\min\),说明:对于 \(1\le i\le m-1\),都有 \(A_{xmin,i}=1\)

这说明,只要我们能走到第 \(xmin\) 行或者第 \((m-1)\) 列,就一定能顺着这条通路到达第 \(n\) 行或者第 \(m\) 列,然后再到达 \((n,m)\)

同样地,列也可以类似地操作。

可以理解为,现在有一个边框,你只要到达了边框就能走到 \((n,m)\) 了。那么每次缩小整个边框地大小,必然是不会更劣的。

于是你一直缩小边框,如果中途无法缩小(\(X_\min\ge Y_\min\)\(Y_\max\le X_\max\)),不可行;否则,如果边框到达了 \((1,1)\),那么就是可行。

实现方面可以采用递归,维护前缀最小值 / 最大值的位置即可。时间复杂度 \(O(T(n+m))\)

bool check(int x, int y)
{
	if (x == 1 || y == 1) return true;
	Node X = preX[x - 1], Y = preY[y - 1];
	if (f[X.min] < g[Y.min]) return check(X.min, y);
	if (g[Y.max] > f[X.max]) return check(x, Y.max);
	return false;
}

正解

特殊性质的提示性非常强。你可以找到 \(X\) 的最小值与 \(Y\) 的最大值的位置