[NOIP2023] 双序列拓展 题解

神题!

题目可以转化成能否使得所有 \(f_i\gt g_i\) 或者 \(f_i\lt g_i\),只考虑处理 \(f_i\lt g_i\) 的情况,\(f_i\gt g_i\) 的情况交换 \(X\)\(Y\) 即可。

首先有一个很简单的 \(O(qnm)\) 的 dp,记 \(dp_{i,j}\) 表示 \(X\) 的前 \(i\) 个数和 \(Y\) 的前 \(j\) 个数是否可以匹配,转移有:

\[dp_{i,j}\leftarrow dp_{i-1,j}\cup dp_{i,j-1}\cup dp_{i-1,j-1},X_i\lt Y_j \]

考虑这个 dp 的本质,实际上是有一个 \(n\times m\) 的网格图,\(A_{i,j}=[X_i\lt Y_j]\)

每次可以走右方的一个 \(1\)、下方的一个 \(1\)、右下方的一个 \(1\),问是否能够走到 \(n,m\)

考虑特殊性质,首先如果满足 \(X_{min}\ge Y_{min}\) 或者 \(X_{max}\ge Y_{max}\),那么是一定无法满足的,在网格图上说也就是有一行或一列全是 \(0\)

排除这种情况后,特殊情况就等价于第 \(n\) 行、第 \(m\) 列都是 \(1\),因为 \(Y\) 中所有数都比 \(X_{min}\) 大,\(X\) 中所有数都比 \(Y_{max}\) 小。

那么只用考虑是否能走到第 \(n-1\) 行或者第 \(m-1\) 列,不难发现这把问题的规模缩小了!

这启发可以继续递归下去,具体就是在剩下的 \(n-1\) 个数找到 \(X\) 的最小、最大,剩下 \(m-1\) 个数中找到 \(Y\) 的最小、最大,然后判断,继续递归即可。

特殊性质对正解的启发是很大的。

对于全部数据,可以考虑找出 \(X_{min}\) 的位置与 \(Y_{max}\) 的位置,它们所占据的两条垂直直线把网格图划分成了四个区域,需要判断的就是能否从左上角走到这两条直线、再从这两条直线走到右下角。

照搬特殊性质的做法,只需要对左上区域递归、对右下区域递归即可。

复杂度 \(O(q(n+m))\)

代码
#include <bits/stdc++.h>

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 5e5 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;

int c, n, m, q;
int a[N], b[N], pa[N], pb[N];

int prex[2][N], prem[2][N], sufx[2][N], sufm[2][N];

void get() {
    int mi = inf, mx = -inf, idi = 0, idx = 0;
    for ( int i = 1; i <= n; i ++) {
        if (mi > a[i]) mi = a[i], idi = i;
        if (mx < a[i]) mx = a[i], idx = i;
        prem[0][i] = idi, prex[0][i] = idx;
    }

    mi = inf, mx = -inf, idi = 0, idx = 0;
    for ( int i = 1; i <= m; i ++) {
        if (mi > b[i]) mi = b[i], idi = i;
        if (mx < b[i]) mx = b[i], idx = i;
        prem[1][i] = idi, prex[1][i] = idx;
    }

    mi = inf, mx = -inf, idi = 0, idx = 0;
    for ( int i = n; i; i --) {
        if (mi > a[i]) mi = a[i], idi = i;
        if (mx < a[i]) mx = a[i], idx = i;
        sufm[0][i] = idi, sufx[0][i] = idx;
    }

    mi = inf, mx = -inf, idi = 0, idx = 0;
    for ( int i = m; i; i --) {
        if (mi > b[i]) mi = b[i], idi = i;
        if (mx < b[i]) mx = b[i], idx = i;
        sufm[1][i] = idi, sufx[1][i] = idx;
    }
}

int chk1( int x, int y) {
    if (x == 1 || y == 1) return 1;

    if (a[prem[0][x - 1]] < b[prem[1][y - 1]]) return chk1(prem[0][x - 1], y);
    if (a[prex[0][x - 1]] < b[prex[1][y - 1]]) return chk1(x, prex[1][y - 1]);

    return 0;
}

int chk2( int x, int y) {
    if (x == n || y == m) return 1;

    if (a[sufm[0][x + 1]] < b[sufm[1][y + 1]]) return chk2(sufm[0][x + 1], y);
    if (a[sufx[0][x + 1]] < b[sufx[1][y + 1]]) return chk2(x, sufx[1][y + 1]);

    return 0;
}

int solve() {
    int F1 = 1, F2 = 1;
    get();
    if (a[1] >= b[1] || a[n] >= b[m]) F1 = 0;
    if (a[prem[0][n]] >= b[prem[1][m]]) F1 = 0;
    if (a[prex[0][n]] >= b[prex[1][m]]) F1 = 0;

    F1 &= (chk1(prem[0][n], prex[1][m]) && chk2(prem[0][n], prex[1][m]));

    swap(a, b), swap(n, m);
    get();
    if (a[1] >= b[1] || a[n] >= b[m]) F2 = 0;
    if (a[prem[0][n]] >= b[prem[1][m]]) F2 = 0;
    if (a[prex[0][n]] >= b[prex[1][m]]) F2 = 0;

    F2 &= (chk1(prem[0][n], prex[1][m]) && chk2(prem[0][n], prex[1][m]));

    swap(a, b), swap(n, m);    
    return (F1 | F2);
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> c >> n >> m >> q;

    for ( int i = 1; i <= n; i ++) cin >> a[i], pa[i] = a[i];
    for ( int i = 1; i <= m; i ++) cin >> b[i], pb[i] = b[i];

    cout << solve();

    for ( int o = 1; o <= q; o ++) {
        int kx, ky;
        cin >> kx >> ky;

        while (kx --) {
            int x, v; cin >> x >> v;
            a[x] = v; 
        }

        while (ky --) {
            int x, v; cin >> x >> v;
            b[x] = v;
        }

        cout << solve();

        for ( int i = 1; i <= n; i ++) a[i] = pa[i];
        for ( int i = 1; i <= m; i ++) b[i] = pb[i];
    }

    return 0;
}
posted @ 2025-10-24 21:18  咚咚的锵  阅读(28)  评论(0)    收藏  举报