Luogu P9870 NOIp2023 雙序列擴充 題解 [ 紫 ] [ 動態規劃 ] [ 分治 ] [ adhoc ]

KS_Fszha發表於2024-11-21

雙序列擴充:很妙的特殊性質類 dp 題,由部分分引導向正解。

題意簡化

你可以把序列 \(X\) 和序列 \(Y\) 中的每一個數複製若干倍並接到這個數後面,問能否構造出一種方案,使得兩個序列長度相等且所有的 \(X_i>Y_i\) 或者 \(X_i<Y_i\)。有 \(60\) 次修改,每次修改都要輸出答案。

觀察

初看這題,只會打一個暴力匹配的暴搜。

然後忽然看到了這題的神秘性質,於是就把這條性質拆分成了兩部分:

  • \(x_1 < y_1\)
  • \(x_n\) 是序列 \(X\) 唯一的一個最小值,\(y_m\) 是序列 \(Y\) 唯一的一個最大值。

第一條性質

我們觀察每個數列的第一項有什麼特別的,容易發現,無論後面的數怎麼複製,兩個數列的第一個數都一定是匹配上的。也就是說,\(x_1\)\(y_1\) 的大小關係就決定了其他 \(x_i\)\(y_i\) 的關係。

於是我們根據每個數列的第一個元素判斷到底是 \(X_i>Y_i\) 還是 \(X_i<Y_i\) 就好了。

由此我們可以設計 dp:

\(dp_{i,j}\) 表示目前序列 \(X\) 匹配到第 \(i\) 個數,序列 \(Y\) 匹配到第 \(j\) 個數可不可行。

轉移顯然有三種:

\[dp_{i,j}=dp_{i-1,j} \bigvee dp_{i,j-1} \bigvee dp_{i-1,j-1} \]

第二條性質

我們觀察到這個方程很像方格取數,把他轉化到方格上來。(這是本題思維難度最大的地方,要有抽象問題的能力)

一個方格 \((i,j)\) 能被走,當且僅當 \(x_i < y_j\)。(在 \(x_1<y_1\) 時)

這裡借一張洛谷題解的圖:

image

發現我們很快能標出所有能被走的點,並且對於第二條性質,如果這個序列要存在,第 \(n\) 個行和第 \(m\) 列的所有數都一定是紅色的。這是有解的不充分必要條件。

為啥呢?因為第 \(n\) 個數是 \(X\) 裡面的最小值,而如果連最小值都無法與 \(Y\) 中的某一個匹配,那麼其他的自然也不可以了。這樣就形成了一行或一列全為空的情況,顯然是無法走過去的。最大值那個也同理。

那麼接下來怎麼求解呢?我們可以採用分支的思路,將 \(X\)\(Y\) 的長度均減 \(1\),遞迴求解即可。(這是因為後面的情況如果合法,那麼右下角的部分一定能走到,因為剩下的部分有解的條件是一個存在長條,長條和原來的十字架組成新的十字架,就保證了一定能走到原來的那個更大的十字架裡)

正解

下面肯定要往最大值最小值上面去想了。假設當前 \(X\) 的最小值在 \(i\)\(Y\) 的最大值在 \(j\),那麼只有第 \(i\) 行和第 \(j\) 列全部是可以走的,當前局面才可能有解。

因為行列交叉形成一個十字架,所以我們遞迴求解左上部分與右下部份的答案即可,兩者都能有解最終才有解。

如下圖:

image

具體實現上,我們分別維護 \(X\) 的字首、字尾最小值、最大值的位置和 \(Y\) 的字首、字尾最小值、最大值的位置即可。一共要維護 \(8\) 個,遞迴進行求解即可。

因為每次分治最多隻會修改一個數,那麼每行每列總共一定不會超過 \(n+m\) 次被遞迴到,所以時間複雜度為 \(O(q(n+m))\)

程式碼

參考了很多洛谷題解的細節與馬蜂,確實是最好寫、最直觀的一種維護方式了。

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
int n,m,a[500005],b[500005],pa[500005],va[500005],pb[500005],vb[500005];
struct node{
    int mx,mn;
}prea[500005],preb[500005],sufa[500005],sufb[500005];
void init()
{
    prea[1]={1,1};
    for(int i=2;i<=n;i++)
    {
        prea[i]=prea[i-1];
        if(a[prea[i].mx]<a[i])prea[i].mx=i;
        if(a[prea[i].mn]>a[i])prea[i].mn=i;
    }
    preb[1]={1,1};
    for(int i=2;i<=m;i++)
    {
        preb[i]=preb[i-1];
        if(b[preb[i].mx]<b[i])preb[i].mx=i;
        if(b[preb[i].mn]>b[i])preb[i].mn=i;
    }
    sufa[n]={n,n};
    for(int i=n-1;i>=1;i--)
    {
        sufa[i]=sufa[i+1];
        if(a[sufa[i].mx]<a[i])sufa[i].mx=i;
        if(a[sufa[i].mn]>a[i])sufa[i].mn=i;
    }
    sufb[m]={m,m};
    for(int i=m-1;i>=1;i--)
    {
        sufb[i]=sufb[i+1];
        if(b[sufb[i].mx]<b[i])sufb[i].mx=i;
        if(b[sufb[i].mn]>b[i])sufb[i].mn=i;
    }
}
bool checkl(int x,int y)
{
    if(x==1||y==1)return 1;
    node nx=prea[x-1],ny=preb[y-1];
    if(a[nx.mn]<b[ny.mn])return checkl(nx.mn,y);
    if(a[nx.mx]<b[ny.mx])return checkl(x,ny.mx);
    return 0;
}
bool checkr(int x,int y)
{
    if(x==n||y==m)return 1;
    node nx=sufa[x+1],ny=sufb[y+1];
    if(a[nx.mn]<b[ny.mn])return checkr(nx.mn,y);
    if(a[nx.mx]<b[ny.mx])return checkr(x,ny.mx);
    return 0;
}
void solve()
{
    bool swp=0;
    if(a[1]==b[1])
    {
        cout<<0;
        return;
    }
    if(a[1]>b[1])
    {
        swap(a,b);
        swap(n,m);
        swp=1;
    }
    init();
    node x=prea[n],y=preb[m];
    if(a[x.mn]>=b[y.mn]||a[x.mx]>=b[y.mx])
    {
        cout<<0;
        if(swp)
        {
            swap(a,b);
            swap(n,m);
        }
        return;
    }
    cout<<(checkl(x.mn,y.mx)&&checkr(x.mn,y.mx));
    if(swp)
    {
        swap(a,b);
        swap(n,m);
    }
}

int main()
{
    //freopen("expand.in","r",stdin);
    //freopen("expand.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int c,q;
    cin>>c>>n>>m>>q;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++)cin>>b[i];
    solve();
    while(q--)
    {
        int kx,ky;
        cin>>kx>>ky;
        for(int i=1;i<=kx;i++)
        {
            cin>>pa[i]>>va[i];
            swap(a[pa[i]],va[i]);
        }
        for(int i=1;i<=ky;i++)
        {
            cin>>pb[i]>>vb[i];
            swap(b[pb[i]],vb[i]);
        }
        solve();
        for(int i=1;i<=kx;i++)
        {
            swap(a[pa[i]],va[i]);
        }
        for(int i=1;i<=ky;i++)
        {
            swap(b[pb[i]],vb[i]);
        }
    }
    return 0;
}

相關文章