给定一个含有 个正整数的序列 ,对于一次操作,你可以任选一个位置 且满足 ,那么就可以移除这个元素,并将后面所有的元素向前移动一位。
对于每个相互独立的询问 需要你求出在前 个元素以及后 个元素不能被移除的情况下,最多可以进行几次操作。
。
首先不难想到让 ,这样问题就转变为每次操作可以删去 的 并且让 减去 。
不难发现 能被删掉当且仅当 中有至少 个能被删掉,因为删掉 的这次操作可以插到删掉 个数之后。
发现询问相当于选一个区间 里的 出来算答案。在线不太好做,考虑离线。
把询问离线到序列上,把询问按 从小到大排序,处理到第 个询问的时候把把 都加入某个可以维护从 开始操作的答案 的数据结构里即可。
考虑怎么维护 ,不难发现它具有单调性,即满足 ,那么设 满足 ,则 对 都有 的贡献。不难发现这个可以用树状数组维护,由于每次都是 这样的区间,所以干脆直接维护每个位置减了多少次,然后找 就可以用树状数组上二分来维护。
代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int S=300005;
struct node
{
int l,r,id;
}que[S];
int n,m;
int a[S],c[S];
int ans[S];
inline void addt(int pos,int x)
{
for(int i=pos;i<=n;i+=i&-i) c[i]+=x;
}
inline int quet(int pos)
{
int res=0;
for(int i=pos;i>=1;i-=i&-i) res+=c[i];
return res;
}
inline int fndk(int k)
{
int pos=0,val=0;
for(int i=20;i>=0;i--)
{
int nxt=pos+(1<<i);
if(nxt<=n&&val+c[nxt]<=k) val+=c[nxt],pos=nxt;
}
return pos;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=i-a[i];
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
que[i].l=x+1,que[i].r=n-y;
que[i].id=i;
}
sort(que+1,que+m+1,[&](node x,node y){return x.r<y.r;});
int j=1;
for(int i=1;i<=m;i++)
{
for(;j<=n&&j<=que[i].r;j++)
{
if(a[j]<0)
{
addt(1,1);
continue;
}
int k=min(fndk(j-1-a[j]),j);
addt(k+1,1);
}
ans[que[i].id]=j-1-quet(que[i].l);
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}