原題鏈接:https://codeforces.ml/problemset/problem/1167/E
題目大意:
給出數組a,a_i<=m,長度爲n
定義操作f(l,r):將數組中 l<=a_i<=r刪除後,得到的新數組。
問有多少對{l,r},使得執行f(l,r)操作後,剩下的序列爲單調不遞減序列
例如:2 3 1 刪掉2 3 後 變爲 1 ,此時單調不遞減,所以f(2,3)是可行操作f(2,3)
題目思路:
卡了一下午的題目叭..
這個題目確實有點驚訝到我(或許是太菜,被輕易驚訝、
首先,可以確定,如果刪除一段區間[l,r]內所有的數之後,新數組單調不遞減,在這基礎上如果刪掉[x,y] x<=l && y>=r 此時新數組絕對還是單調不遞減,由此可見:刪除操作具有單調性。
瞭解單調之後,考慮求區間個數採取尺取 或者 二分
之後便有了統一套路,可以不可以求出以l開頭的所有合法區間?
所以只需要找到右邊第一個合法的,那麼向右擴充的區間全部合法
假設要刪除的區間爲[l,r] ,如果刪除l,r區間後合法
那麼需要滿足[1,l-1]是單調不遞減的的,[r+1,m]也是不遞減的,並且滿足 [1,l-1]所有的數 都在 [r+1,n]的左邊即可
那麼[l,r]即爲合法區間
所以:
1.需要處理 1,pref 是否可行的最遠的pref,當l-1大於pref時即不可行
2.需要處理 curf,n 是否可行的最近的curf,當r+1小於curf時即不可行
3.0 一定可行 n+1一定可行
4.之後我們預處理出 每個a_i 最左邊出現的位置,與最右邊出現的位置,當且僅當R[l-1]<L[r+1]時 區間可行
5.考慮到一些未出現的數字,5未出現但是還需要和6 作比較,那麼取小於5的第一個數作比較,這裏使用並查集進行優化。
6.最後套一下尺取模板,解決了問題:
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=1e17;
const int maxn=2e6+6;
const int mod=1e9+7;
const double eps=1e-9;
const double PI = acos(-1);
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
int L[maxn],R[maxn];
int vis[maxn];
ll num[maxn];
int ls=0,rs=m+1;
int pre[maxn],cur[maxn];
int Find_pre(int x){
if(!x) return x;
if(!vis[x]) return pre[x]=Find_pre(pre[x]);
return x;
}
int Find_cur(int x){
if(x==m+1) return x;
if(!vis[x]) return cur[x]=Find_cur(cur[x]);
return x;
}
bool check(int l,int r){
if(l-1<=ls&&r+1>=rs){
int dx=Find_pre(l-1);
int dy=Find_cur(r+1);
if(R[dx]<L[dy]) return true;
}
return false;
}
int main(){
read(n);read(m);
for(int i=1;i<=m;i++) cur[i]=i+1,pre[i]=i-1;
R[0]=0;
L[m+1]=n+5;
for(int i=1;i<=n;i++){
read(num[i]);
if(!L[num[i]]) L[num[i]]=i;
R[num[i]]=i;
vis[num[i]]=1;
}
int lst=0;
for(int i=1;i<=m;i++){
if(!L[i]){
ls=i;
continue;
}
if(L[i]<lst) break;
ls=i;
lst=R[i];
}
lst=n+5;
for(int i=m;i>=1;i--){
if(!L[i]){
rs=i;
continue;
}
if(R[i]>lst) break;
rs=i;
lst=L[i];
}
ll ans=0;
int s=1;
for(int i=1;i<=m;i++){
while(s<=i&&check(s,i)){
ans+=m-i+1;
s++;
}
}
printf("%lld\n",ans);
return 0;
}
/**
###
**/
總結:
1.這個題的思路確實很妙,我一直卡就卡在怎樣判斷l,r區間是否可行,沒想出O1的辦法。換個思路:從[l,r]刪除後的結果來反推,預處理即可O1判斷區間可行