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;
}
posted @ 2020-09-14 22:06  ycx060617  阅读(280)  评论(0编辑  收藏  举报