P9870 [NOIP2023] 双序列拓展 题解
NOIP2023 T3,特殊性质题。
什么是特殊性质题?就是题目给出了你极其神秘的性质,从而引导你想出正解。
本篇题解将从部分分的角度,一步步讲述部分分与正解的关系。这样看的话,本题会变得十分简单。
\(\text{Task } 1∼7, O(Tnm)\)
简化题意其实就是要求满足最终序列里的 \((f_i-g_i)\) 同号。那么我们只考虑 \(f_i<g_i\) 的情形,另一种是等价的。
看上去 \(l_0\) 很大,但其实只需要计算出 \(X,Y\) 的 \(L\) 序列一一匹配即可。记匹配序列分别为 \(x,y\)。
那么对于 \(O(nm)\) 的 dp,套路地设 \(dp_{i,j}\) 表示 \(x\) 匹配到 \(i\),\(y\) 匹配到 \(j\) 的合法性。从 \((i,j)\) 转移到 \((i,j+1)\),\((i+1,j)\) 是容易的。
代码:
int DP() {
if (x[1] == y[1]) return 0;
int fg = 0;
if (y[1] > x[1]) fg = 1;
memset(dp, 0, sizeof dp);
dp[1][1] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (dp[i][j]) {
if (fg == 0 && x[i + 1] > y[j]) dp[i + 1][j] = 1;
if (fg == 1 && y[j] > x[i + 1]) dp[i + 1][j] = 1;
if (fg == 0 && x[i] > y[j + 1]) dp[i][j + 1] = 1;
if (fg == 1 && y[j + 1] > x[i]) dp[i][j + 1] = 1;
}
return dp[n][m];
}
\(\text{Task } 8∼14, O(T(n+m))\) 特殊性质
dp 的状态是难以优化的,考虑特殊性质是什么意思。
\(x\) 的最小值在序列的最后端,说明这个做法大概和维护一些最小值相关联。又因为这是一个线性的复杂度,使我们想到贪心处理。这样的通常套路是枚举一个变量,同时双指针统计第二个变量去做。
simple 的想法是枚举 \(1\sim m\) 的 \(y\),对于每一个 \(y\) 拓展 \(x\),直到不能拓展为止。并检查合法性。
但这样显然没有正确性,原因是当前 \(y\) “占了过多” 的 \(x\),导致没有足够的合法 \(x\) 和之后的 \(y\) 匹配。
如何解决这个问题?要让更多的 \(y\) 得到匹配,就是不让当前的 \(y\) 去挤占可以去做贡献的更小的 \(x\),也就是 让每个最小的 \(x\) 去拓展更多的 \(y\)。 我们转而枚举 \(x\),对于每个 前缀最小值 \(x_i\),我们可以让它尽情放飞地去匹配尽量多的 \(y\),假设匹配到了 \(y_i\),下一个前缀最小值是 \(x_j\),那么 \([x_i,x_{j-1}]\) 直接匹配 \([y_1,y_i]\) 中的最大值 \(y_{\max}\) 是最优的,至于 \(y_{\max}\) 之后的部分,由于是 \(x_i\) 拓展到的,还有 \(x_j\) 等着匹配这些个拓展出的部分,且让 \(x_j\) 去拓展新的序列也一定更优。这样一来事实上形成了一些个区间。由于 \(y\) 的最大值同样在序列末尾,并不存在 \(x\) 到终点之后没有 \(y\) 与之匹配的情形。那么对于不是前缀最小值的 \(x\),检查它和 \(y_{\max}\) 的大小关系即可。这样做正确的核心是在恰当的时机让当前匹配的 \(y\) "放手",避免 "侵占资源",让那个 \(x\) 来拓展更多的 \(y\)。
代码:
int chk() {
for (int i = 1; i <= n; i++)
mx[i] = max(mx[i - 1], y[i]);
int j = 0, mn = 1e9;
for (int i = 1; i <= n; i++) {
if (x[i] < mn) {
while (j < m && x[i] < y[j + 1])
++j;
}
else if(x[i] >= mx[j]) return 0;
mn = min(mn, x[i]);
}
return j == m;
}
\(\text{Task } 15\sim 20, O(T(n+m))\) 正解
特殊性质的提示还 TM 不够明显吗??
将 \(x,y\) 序列分别按照最大,最小值分成两段处理即可。
完整代码:
#include <bits/stdc++.h>
#define N 500005
#define int long long
using namespace std;
int c, n, m, q;
int x[N], y[N], xx[N], yy[N];
int mx[N];
int nx[N], ny[N];
int chk(int p, int q) {
if (nx[1] >= ny[1]) return 0;
mx[0] = -1e9;
for (int i = 1; i <= q; i++)
mx[i] = max(mx[i - 1], ny[i]);
int j = 0, mn = 1e9;
for (int i = 1; i <= p; i++) {
if (nx[i] < mn) {
while (j < q && nx[i] < ny[j + 1])
++j;
}
else if(nx[i] >= mx[j]) return 0;
mn = min(mn, nx[i]);
}
return j == q;
}
int kudo() {
if (x[1] == y[1]) return 0;
if (x[1] > y[1]) {
swap(x, y);
swap(n, m);
}
int mn = 1e9, p = 0;
for (int i = 1; i <= n; i++)
mn = min(mn, x[i]);
for (int i = 1; i <= n; i++)
if (mn == x[i]) {
p = i;
break;
}
int mx = -1e9, q = 0;
for (int i = 1; i <= m; i++)
mx = max(mx, y[i]);
for (int i = 1; i <= m; i++)
if (mx == y[i]) {
q = i;
break;
}
int cp = 0, cq = 0;
for (int i = 1; i <= p; i++)
nx[++cp] = x[i];
for (int i = 1; i <= q; i++)
ny[++cq] = y[i];
int ans = chk(cp, cq);
cp = 0, cq = 0;
for (int i = n; i >= p; i--)
nx[++cp] = x[i];
for (int i = m; i >= q; i--)
ny[++cq] = y[i];
ans &= chk(cp, cq);
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> c >> n >> m >> q;
for (int i = 1; i <= n; i++)
cin >> x[i], xx[i] = x[i];
for (int i = 1; i <= m; i++)
cin >> y[i], yy[i] = y[i];
int nn = n, mm = m;
cout << kudo();
for (int w = 1; w <= q; w++) {
int nx, ny;
cin >> nx >> ny;
while (nx--) {
int p, t;
cin >> p >> t;
x[p] = t;
}
while (ny--) {
int p, t;
cin >> p >> t;
y[p] = t;
}
cout << kudo();
n = nn, m = mm;
for (int i = 1; i <= n; i++)
x[i] = xx[i];
for (int i = 1; i <= m; i++)
y[i] = yy[i];
}
return 0;
}
标签:return,匹配,int,题解,拓展,P9870,序列,NOIP2023,dp
From: https://www.cnblogs.com/Rock-N-Roll/p/18548583