【UR #13】Yist
阿新 • • 發佈:2020-08-18
UOJ小清新題表
題目摘要
給出一個排列 \(A\) 以及它的一個非空子序列 \(B\),給出一個 \(x\) 並進行若干次操作,每一次操作需要在 \(A\) 中選擇一個長度恰好為 \(x\) 的區間並刪除它的最小值。如果在操作結束以後剩下的陣列恰好是 \(B\),那麼就可以得到 \(x\) 分,否則得到 \(0\) 分。
有 \(q\) 組詢問,所有的 \(A\) 序列都是一樣的,但 \(B\) 序列不同。求每次詢問能得到的最大得分。
\(B\) 序列是一個 01
串,若該位置上為 \(1\) ,則表示 \(A\) 序列中該位置的數在 \(B\) 序列中出現了。
資料範圍
\(2≤n≤1000000\)
思路
可以先看看樣例和解釋。
我們可以列舉每一個可能要被刪除的點。若其要被刪除,向左擴充套件到第一個比他小的點 \(l\),向右擴充套件到地一個比他小的 \(r\),那麼這兩個點構成的開區間 \((l,r)\) 就是這個點要被刪除時的極大區間。由於要保證必須滿足條件,所以要在所有的極大區間中取最小,即為所要求的 \(x\)。
維護區間大小或者聯通性之類的這種東西,很容易可以想到並查集。可以對下標開兩個並查集,分別向左向右擴充套件。
比如要找到左邊第一個比當前點小的點,需要把數從大到小加入,用並查集維護不用刪除的點,也就是 \(B\)
一開始用字首和維護一下 \(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; }