CF1404C:Fixed Point Removal(离线)(树状数组二分)

解析

写了不少线段树上二分,原来树状数组上也是可以二分的

首先如果 a i > i a_i>i ai>i,那必然无法删除,下面只考虑 a i < = i a_i<=i ai<=i的情况
本题试图离线不难想到,但我一开始总是按照刻板思维尝试按序移动左端点,结果根本没法做…
反过来想,移动右端点就可做多了
f i f_i fi表示当前右端点为r的情况下,[i,r]最多可以删掉的数量
那么我们当把r移动到r+1时,只有 f i > = i − a i f_i>=i-a_i fi>=iai时才会对 f i f_i fi产生1的贡献
不难发现这个f是单调不升的,所以符合上面那个条件的f应该是一段前缀,可以利用二分求出
然后就是简单的对f区间+1,用树状数组维护即可

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+100;
const int mod=1e9+7;
#define ll long long
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();};
	while(isdigit(c)){x=x*10+c-'0';c=getchar();};
	return x*f;
}

int n,m;

int f[N],a[N],mi[20];
inline void add(int p,int v){
	for(;p<=n;p+=p&-p) f[p]+=v;
	return;
}
inline int ask(int p){
	int res(0);
	for(;p;p-=p&-p) res+=f[p];
	return res;
}
inline int find(int val){
	int res=0,pl=0;
	for(int k=18;k>=0;k--){
		if(pl+mi[k]>n||res+f[pl+mi[k]]<val) continue;
		pl+=mi[k];res+=f[pl];
	}
	return pl;
}
struct query{
	int l,r,id;
	bool operator < (const query oth)const{return r<oth.r;}
}p[N];
int ans[N];
int main(){
	#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	#endif
	mi[0]=1;
	for(int i=1;i<=18;i++) mi[i]=mi[i-1]<<1;
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=i-read();
	for(int i=1;i<=m;i++) p[i]=(query){(int)read()+1,n-(int)read(),i};
	sort(p+1,p+1+m);
	int pl=1;
	//for(int i=1;i<=n;i++) printf("%d ",a[i]);
	//putchar('\n');
	for(int i=1;i<=n;i++){
		if(a[i]>=0){
			int pl=find(a[i]);
			//printf("i=%d pl=%d\n",i,pl);
			add(1,1);add(min(pl+1,i+1),-1);
		}
		while(pl<=m&&p[pl].r==i){
			ans[p[pl].id]=ask(p[pl].l);
			pl++;
		}
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}
/*
2 3
7 4 9 9
1 2 8
3 1
4 2 4
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值