1. 程式人生 > 實用技巧 >【UR #13】Yist

【UR #13】Yist

UOJ小清新題表

題目摘要

UOJ連結

給出一個排列 \(A\) 以及它的一個非空子序列 \(B\),給出一個 \(x\) 並進行若干次操作,每一次操作需要在 \(A\) 中選擇一個長度恰好為 \(x\) 的區間並刪除它的最小值。如果在操作結束以後剩下的陣列恰好是 \(B\),那麼就可以得到 \(x\) 分,否則得到 \(0\) 分。

\(q\) 組詢問,所有的 \(A\) 序列都是一樣的,但 \(B\) 序列不同。求每次詢問能得到的最大得分。

\(B\) 序列是一個 01 串,若該位置上為 \(1\) ,則表示 \(A\) 序列中該位置的數在 \(B\) 序列中出現了。

資料範圍

\(2≤n≤1000000\)

\(1≤q≤10\)\(A\) 為一個排列,\(B\)\(A\) 的非空子序列,且 \(B≠A\)

思路

可以先看看樣例和解釋。

我們可以列舉每一個可能要被刪除的點。若其要被刪除,向左擴充套件到第一個比他小的點 \(l\),向右擴充套件到地一個比他小的 \(r\),那麼這兩個點構成的開區間 \((l,r)\) 就是這個點要被刪除時的極大區間。由於要保證必須滿足條件,所以要在所有的極大區間中取最小,即為所要求的 \(x\)

維護區間大小或者聯通性之類的這種東西,很容易可以想到並查集。可以對下標開兩個並查集,分別向左向右擴充套件。

比如要找到左邊第一個比當前點小的點,需要把數從大到小加入,用並查集維護不用刪除的點,也就是 \(B\)

序列中的每個 \(1\), 如果遇到 \(1\) ,則 \(fa[i]=i\) ,否則 \(fa[i]=\text{Find}(\ i-1\ )\) 。顯然最後查詢的時候需要跳過 \(1\) 。向右擴充套件同理。這樣你每次都能找到極大區間,只需要取個 \(\min\) 即可。

一開始用字首和維護一下 \(1\) 的個數,此點對應的極大區間就是擴充套件後的區間中 \(1\) 的個數加上自己(\(+1\))。

程式碼

建議改成:三目運算子帶師

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3f3f3f3f;
int n,ans;
int a[maxn],pos[maxn],L[maxn],R[maxn],sum[maxn];
char s[maxn];

inline int read(){
    int x=0,fopt=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=-1;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
    return x*fopt;
}

int Find(int x,int fa[]){
    return x==fa[x]?x:(fa[x]=Find(fa[x],fa));
}

inline void Solve(){
    for(int i=1;i<=n;i++)
        sum[i]=(s[i]=='1')?sum[i-1]+1:sum[i-1];
    ans=INF;R[n+1]=n+1;
    for(int i=1;i<=n;i++)
        L[i]=(s[i]=='1')?i:Find(i-1,L);
    for(int i=n;i>=1;i--)
        R[i]=(s[i]=='1')?i:Find(i+1,R);
    for(int i=n;i>=1;i--){
        int v=pos[i];
        if(s[v]=='1'){
            L[v]=Find(v-1,L);
            R[v]=Find(v+1,R);
        }else ans=min(ans,sum[Find(v,R)-1]-sum[Find(v,L)]+1);//注意是開區間
    }
}

int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        pos[a[i]]=i;
    }
    int Q=read();
    while(Q--){
        scanf("%s",s+1);
        Solve();
        printf("%d\n",ans);
    }
    return 0;
}