CF1404C Fixed Point Removal


題目大意

題目鏈接

給定一個正整數序列\(a_{1},\dots ,a_{n}\)。你可以進行若干次操作,每次操作可以選擇一個滿足\(a_i=i\)的位置\(i\),將其刪去。刪去后,左右兩邊會自動拼合到一起(也就是右邊所有數下標都會相應地變化)。

你想要最大化刪去的元素數量。

但是這個問題太簡單了,所以你需要回答\(q\)次詢問。每次詢問給定兩個正整數\(x,y\),求如果將序列的前\(x\)個數和后\(y\)個數變成\(n+1\)(也就是強制它們不得被刪去),新的序列里最多能刪掉多少個數呢?

注意,每次詢問是獨立的,也就是說詢問后,序列會復原。

數據范圍:\(1\leq n,q\leq 3\times 10^5\)\(1\leq a_i\leq n\)\(x,y\geq 0,x+y< n\)

本題題解

先不考慮多次詢問。只針對一個給定的序列,如何求答案呢?

我們發現,后面的數被刪除時,不會對前面的數產生影響。因此,我們總能通過巧妙地安排刪除順序,使得所有“理論上能被刪除的數”都被刪掉。

例如,初始時的一個位置\(i\),滿足\(a_i=i-2\)(也就是\(a_i\)需要向前移動\(2\)格才能被刪掉)。假設,我們已經知道了\(i\)前面有\(5\)個能被刪除的數(即我們能構造出一種刪除它們的方案)。那我們只要在這個方案進行到第\(2\)步時(此時\(a_i\)向前移動了\(2\)格,滿足了\(a_i=i\)),把\(a_i\)刪掉。然后再繼續刪除后\(3\)個數即可。因為\(i\)的位置在它們后面,所以中間插入一個“刪除\(a_i\)”的操作,不會對原本的構造產生影響。於是我們就在“刪除前\(i-1\)個數的方案”的基礎上,構造出了“刪除前\(i\)個數的方案”。用這種歸納,可以證明上一段所說的“應刪盡刪”的結論。

形式化地,我們設\(f_i\)表示序列的前\(i\)個位置,最多有多少位置被刪掉。則:

\[f_i=f_{i-1}+[i\geq a_i\text{ and }f_{i-1}\geq i-a_i] \]

如果沒有多次詢問,我們現在可以\(O(n)\)求出一個序列的答案。


考慮多次詢問。

此時有兩種方向,一種是直接用數據結構維護區間,把整體問題搬到區間上,例如用線段樹或者分塊。但是我們發現,這個\(f\)是需要遞推的,也就是說,前面一個\(f\)變化,可能會造成連鎖反應。因此不好直接維護區間。

另一種方向是發現問題性質中的可二分性。我們來具體說說。

我們稱【把序列的\(x\)個數強行變成\(n+1\)】為【ban掉\(x\)個數】。

對於任何一個位置,顯然是它前面ban掉的數越少,它越可能可以被刪除。那么,對每個位置\(i\),預處理出一個\(\text{lim}_i\) (\(-1\leq \text{lim}_i<i\)),表示ban掉\(\leq \text{lim}_i\)個數時,\(i\)這個位置是可以被刪除的;ban掉\(>\text{lim}_i\)個數,\(i\)這個位置就不能被刪除了。這也就是我說的,“發現問題性質中的可二分性”。

如何預處理\(\text{lim}_i\)?考慮對每個\(i\)二分答案。然后相當於要查詢前\(i-1\)個位置里,滿足\(\text{lim}_j\geq \text{mid}\)\(j\)有多少個 (\(1\leq j<i\))。可以用主席樹維護。我們甚至可以直接在主席樹上二分,這樣復雜度從\(O(n\log^2n)\)降為\(O(n\log n)\)

知道了\(\text{lim}_1,\dots ,\text{lim}_n\)后,如何求答案?相當於要查詢,\(j\in[1,n-y]\)里,滿足\(\text{lim}_j\geq x\)\(j\)有多少個。可以直接在主席樹上查詢。時間復雜度\(O(q\log n)\)

總時間復雜度\(O((n+q)\log n)\)

總結反思

這個題給了我們一個很好的啟示:處理多次詢問的問題時,不一定要用數據結構大力維護出區間答案。可以嘗試發現問題里的可二分性,然后實現“降維”。例如本題里,這種思路就成功地去掉了\(x\)這一維,我們只需要簡單地用數據結構維護\(y\)的答案即可。

有一類經典問題,多次詢問,每次問一個區間\([l_i,r_i]\)是否可行。如果發現了可二分性,那么一個做法是:對每個\(r\in[1,n]\),預處理出能夠使它合法的最小/最大的\(l\)。這個\(l\)可以二分,有時也可以直接用 two pointers 求。這個經典做法,與本題的思路多少有些異曲同工之妙。

參考代碼

Codeforces提交記錄

//problem:CF1404C
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=3e5;
int n,q,a[MAXN+5],b[MAXN+5],lim[MAXN+5];
struct FrogTree{
	int rt[MAXN+5],sum[MAXN*40],ls[MAXN*40],rs[MAXN*40],cnt;
	void ins(int& x,int y,int l,int r,int pos){
		x=++cnt;
		sum[x]=sum[y]+1;
		ls[x]=ls[y];
		rs[x]=rs[y];
		if(l==r) return;
		int mid=(l+r)>>1;
		if(pos<=mid)
			ins(ls[x],ls[y],l,mid,pos);
		else
			ins(rs[x],rs[y],mid+1,r,pos);
	}
	int binary_search(int x,int l,int r,int b){
		// 最短的, 和>=b的后綴
		if(l==r)
			return l;
		int mid=(l+r)>>1;
		if(sum[rs[x]] >= b)
			return binary_search(rs[x],mid+1,r,b);
		else
			return binary_search(ls[x],l,mid,b-sum[rs[x]]);
	}
	int query(int x,int l,int r,int ql,int qr){
		if(!x) return 0;
		if(ql<=l && qr>=r){
			return sum[x];
		}
		int mid=(l+r)>>1;
		int res=0;
		if(ql<=mid)
			res += query(ls[x],l,mid,ql,qr);
		if(qr>mid)
			res += query(rs[x],mid+1,r,ql,qr);
		return res;
	}
	FrogTree(){}
}T;
int main() {
	cin>>n>>q;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		if(i<a[i]){
			T.rt[i]=T.rt[i-1];
			b[i]=-1;
			lim[i]=-1;
			continue;
		}
		b[i]=i-a[i];
		if(T.sum[T.rt[i-1]] < b[i]){
			lim[i]=-1;
			T.rt[i]=T.rt[i-1];
			continue;
		}
		if(!b[i])
			lim[i]=i-1;
		else
			lim[i] = T.binary_search(T.rt[i-1],0,n-1,b[i]);
		T.ins(T.rt[i],T.rt[i-1],0,n-1,lim[i]);	
	}
	//for(int i=1;i<=n;++i)
	//	cerr<<lim[i]<<" ";
	//cerr<<endl;
	while(q--){
		int x,y;
		cin>>x>>y;
		cout<<T.query(T.rt[n-y],0,n-1,x,n-1)<<endl;
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM