1. 程式人生 > 實用技巧 >CodeForces 1404C - Fixed Point Removal

CodeForces 1404C - Fixed Point Removal

這是我所做不出來的題目。我什麼時候才能 GM 啊?讓我回個 IM 也好嘛!

洛谷題目頁面傳送門 & CF 題目頁面傳送門

題意見洛谷。

首先探索一個下標序列能夠全部被刪除的充要條件。我們設 \(b_i=i-a_i\),那麼任意時刻,顯然 \(a_i\) 能被刪掉當且僅當 \(b_i=0\)。於是我們盯著這個 \(b\) 看:一開始肯定要有一個先驅者 \(b_i=0\) 對不對,然後刪除之後就可以讓後面的 \(b\) 值都減去 \(1\)。一個 \(b_i\),如果它 \(<0\) 那顯然沒救了;如果 \(\geq 0\) 那麼只需要將它減去 \(b_i\)\(1\) 就變成 \(0\)

可以被刪掉了。也就是說一個 \(b_i\geq 0\) 的位置 \(i\) 要被刪掉必須滿足前面有 \(b_i\) 個已經被刪掉了。於是我們得到了一個必要條件:下標序列 \(c\) 能被全部刪掉僅當 \(c_i\leq i-1\)

然後隨便 van van 發現這個也滿足充分性的,只需要構造出一個方案即可證明。那麼方案很好構造,就每次刪掉 \(c\) 中最右邊滿足 \(b_{c_i}=0\)\(c_i\) 即可。

然後再對詢問進行一番顯然的轉化:每組詢問 \(x,y\) 相當於求 \(b\) 上的區間 \([l=x+1,r=n-y]\) 的最長能被 \([0,1,2,3,\cdots]\)

所「覆蓋」的子序列長度。就變成了一個看上去很清新的 DS 題。

如何求呢??

暫時想不到直接對區間維護的,不妨假設區間左端點固定,來求(這個其實是老套路了)。那麼可以維護以每個位置為最後一位的最長子序列長度 \(-1\),這是一個 \(\mathrm O(n)\) 的數列對吧。如何求這個數列很容易,類似 DP 的,當前位置 \(i\) 答案為 \(x\) 要滿足兩個條件:

  1. 前面有 \(x-1\) 長度的,即 \(\max\limits_{j=1}^{i-1}\{dp_j\}\geq x-1\)
  2. 當前這位要滿足,即 \(x\geq a_i\)

綜合起來就是 \(x\in\left[a_i,\max\limits_{j=1}^{i-1}\{dp_j\}+1\right]\)

。要取最長的,於是如果區間不為空就取右端點,如果為空就設一個錯誤值,比如 \(-\infty\) 吧(可能會比較方便)。然後如果光是左端點固定的話,這個很顯然是可以 \(\mathrm O(n)\) 的。可惜固定不得。

於是考慮從右往左列舉左端點,相當於依次 push_front 然後維護整個 DP 陣列(跟上面套路配套的套路)。考慮如何維護。

顯然任意時刻 DP 陣列是在 \([0,1,2,3,\cdots]\) 裡面插若干個 \(-\infty\) 這個形式。然後發現在往前面 push 的過程中,push 的是 \(0\),所以這個自然數序列要往後順移,即全體加 \(1\)。然後所有 \(-\infty\) 當年所對應的區間也在加 \(1\),慢慢加的話可能有朝一日能使那個區間不為空,重獲新生。又顯然最多會有 \(n\) 次重獲新生的說,所以這個就暴力修改就可以了(每個時刻從前往後依次修改,每次修改還要附帶字尾加 \(1\))。那麼如何每次快速找到復活的人們呢?而且不僅要找到,還要每次找到的是最前面的?維護一個線段樹,然後線段樹二分即可。

最後,這個「老套路」離線 + 線段樹實現是比較方便的,但是注意到每次是在前一個歷史版本上直接修改,也可以用主席樹預處理來實現回答詢問的線上,不過我不會,而且也很簡單的樣子,不管了。

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int inf=0x3f3f3f3f;
const int N=300000,QU=N;
int n,qu;
int a[N+1];
struct query{int l,r;}qry[QU+1];
vector<int> pos[N+1];
int ans[QU+1];
struct segtree{
	struct node{int l,r,dif,rl,lz;}nd[N<<2];
	#define l(p) nd[p].l
	#define r(p) nd[p].r
	#define dif(p) nd[p].dif
	#define rl(p) nd[p].rl
	#define lz(p) nd[p].lz
	void bld(int l=1,int r=n,int p=1){
		l(p)=l;r(p)=r;dif(p)=rl(p)=-inf;lz(p)=0;
		if(l==r)return;
		int mid=l+r>>1;
		bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
	}
	void init(){bld();}
	void sprup(int p){dif(p)=max(dif(p<<1),dif(p<<1|1));rl(p)=max(rl(p<<1),rl(p<<1|1));}
	void sprdwn(int p){
		if(lz(p)){
			dif(p<<1)+=lz(p);rl(p<<1)+=lz(p);lz(p<<1)+=lz(p);
			dif(p<<1|1)+=lz(p);rl(p<<1|1)+=lz(p);lz(p<<1|1)+=lz(p);
			lz(p)=0;
		}
	}
	void chg(int x,int v1,int v2,int p=1){
		if(l(p)==r(p))return dif(p)=v1,rl(p)=v2,void();
		sprdwn(p);
		int mid=l(p)+r(p)>>1;
		chg(x,v1,v2,p<<1|(x>mid));
		sprup(p);
	}
	void add(int l,int r,int v,int p=1){
		if(l>r)return;
		if(l<=l(p)&&r>=r(p))return dif(p)+=v,rl(p)+=v,lz(p)+=v,void();
		sprdwn(p);
		int mid=l(p)+r(p)>>1;
		if(l<=mid)add(l,r,v,p<<1);
		if(r>mid)add(l,r,v,p<<1|1);
		sprup(p);
	}
	int mx(int l,int r,int p=1){
		if(l<=l(p)&&r>=r(p))return rl(p);
		sprdwn(p);
		int mid=l(p)+r(p)>>1,res=-inf;
		if(l<=mid)res=max(res,mx(l,r,p<<1));
		if(r>mid)res=max(res,mx(l,r,p<<1|1));
		return res;
	}
	int fst(){
		int p=1;
		if(dif(p)<0)return 0;
		while(l(p)<r(p)){
			sprdwn(p);
			if(dif(p<<1)>=0)p=p<<1;
			else p=p<<1|1;
		}
		return l(p);
	}
}segt;
int main(){
	cin>>n>>qu;
	for(int i=1;i<=n;i++)scanf("%d",a+i),a[i]=i-a[i];
	for(int i=1;i<=qu;i++)scanf("%d%d",&qry[i].l,&qry[i].r),qry[i].l++,qry[i].r=n-qry[i].r,pos[qry[i].l].pb(i);
	segt.init();
	for(int i=n;i;i--){
		if(a[i]==0){
			segt.chg(i,-inf,0);segt.add(i+1,n,1);
			int fd;
			while(fd=segt.fst()){
				segt.chg(fd,-inf,segt.mx(1,fd-1)+1);segt.add(fd+1,n,1);
			}
		}
		else if(a[i]>0)segt.chg(i,-a[i],-inf);
		for(int j=0;j<pos[i].size();j++)ans[pos[i][j]]=max(0,segt.mx(1,qry[pos[i][j]].r)+1);
	}
	for(int i=1;i<=qu;i++)printf("%d\n",ans[i]);
	return 0;
}