[cf1405E]Fixed Point Removal(平衡树+树状数组)

编号为1-n的纸牌,按一定顺序排列,每次可以移走放在第i个且编号为i的纸牌,右边的自动向左补齐。每次询问限定了左边x张和右边y张不能动,问最多移走多少张。

好像和电脑里的“纸牌”有点像?

为了移走尽量多的话,如果同时有多张可以拿,显然应该先拿最右边的,这样保证其他已经可以被拿的牌不受影响,且可能产生更多能被拿走的牌。每次先拿走一张,之后右边所有牌序号-1,这个操作似乎让我们想到了平衡树。

如果不考虑多次有限制的询问,只考虑原始序列能拿走多少张,显然简单的平衡树操作就可以处理。考虑如果有限制,会影响拿牌的最优顺序吗?显然不会——拿牌的策略没有变。初始时每个点的值为它的位置减自身编号,代表左边再拿走几张,它就可以被拿走,如果是负值就赋个inf。一个节点还需要记录它子树中最靠右的最小值是多少以及最小值的编号,每进行一轮,如果根的最右最小值为0,就可以再拿走一张。同时考虑到一会儿还有限制,我们先记一下每个节点初始的位置,待会儿能看到有什么用。

这样我们得到了一个序列,如果所有牌都可拿,拿牌的最优操作顺序。那么来想想限制会如何改变答案:如果一张牌在某次询问中因为太靠左不能拿,那在我们得到的操作顺序中,它后面的牌都拿不了;如果因为太靠右不能拿,那在原序列中位置比它靠右且存在于操作顺序中的牌都拿不了。对于左端限制,我们可以在求完操作顺序数组que后,直接求一个apr数组,apr[i]代表小于等于i的数,最早出现在que中第几个——因为左端限制的是前x个而不是第x个,所以要求的是所有小于等于它的。对于右端限制,我们发现要离线一下询问,按y从大到小排序,每次删掉yi到y(i+1)-1的贡献。然后求1~(apr[xi]-1)的牌数和,这里使用一下树状数组,把修改和询问都变成log的,就大功告成了。

我这个方法可能比较繁琐,不过感觉思路也蛮直接吧,就注意一下各种数组别用乱了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300010,inf=1e9;
struct node{
	int l,r,fa,x,f,mn,pos,pre;
}tree[N];
struct qry{
	int x,y,id,ans;
}q1[N]; 
int n,q,num,cnt,root,a1[N],delta[N],tim[N],apr[N],que[N],aque[N];
inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x;
}
inline void new1(int p,int f){
	tree[p].x-=f,tree[p].mn-=f,tree[p].f+=f;
}
inline void pushdown(int p){
	if(tree[p].f){
		if(tree[p].l)new1(tree[p].l,tree[p].f);
		if(tree[p].r)new1(tree[p].r,tree[p].f);
		tree[p].f=0;
	}
}
inline void update(int p){
	if(tree[tree[p].r].mn<=tree[p].x&&tree[tree[p].r].mn<=tree[tree[p].l].mn)
		tree[p].mn=tree[tree[p].r].mn,tree[p].pos=tree[tree[p].r].pos;
	else if(tree[p].x<=tree[tree[p].l].mn)
		tree[p].mn=tree[p].x,tree[p].pos=p;
	else tree[p].mn=tree[tree[p].l].mn,tree[p].pos=tree[tree[p].l].pos;
}
inline void left_rotate(int p){
	int q=tree[p].fa,r=tree[q].fa;
	pushdown(r),pushdown(q),pushdown(p);
	tree[q].r=tree[p].l;
	if(tree[p].l)tree[tree[p].l].fa=q;
	tree[p].l=q,tree[q].fa=p;
	update(q),update(p);
	tree[p].fa=r;
	if(r){
		if(tree[r].l==q)tree[r].l=p;
		else tree[r].r=p;
	}
}
inline void right_rotate(int p){
	int q=tree[p].fa,r=tree[q].fa;
	pushdown(r),pushdown(q),pushdown(p);
	tree[q].l=tree[p].r;
	if(tree[p].r)tree[tree[p].r].fa=q;
	tree[p].r=q,tree[q].fa=p;
	update(q),update(p);
	tree[p].fa=r;
	if(r){
		if(tree[r].l==q)tree[r].l=p;
		else tree[r].r=p;
	}
}
inline int getlr(int p){
	return tree[tree[p].fa].l==p;
}
inline void rotate(int p){
	if(getlr(p))right_rotate(p);
	else left_rotate(p);
}
inline void splay(int p,int tar){
	while(tree[p].fa!=tar&&tree[tree[p].fa].fa!=tar){
		if(getlr(tree[p].fa)==getlr(p))rotate(tree[p].fa),rotate(p);
		else rotate(p),rotate(p);
	}
	if(tree[p].fa!=tar)rotate(p);
	if(!tar)root=p; 
}
int build(int a,int b){
	int p=++num,mid=(a+b)>>1;
	tree[p].fa=tree[p].l=tree[p].r=tree[p].f=0,
	tree[p].mn=tree[p].x=delta[mid],tree[p].pos=p,tree[p].pre=mid;
	if(a<mid){tree[p].l=build(a,mid-1);tree[tree[p].l].fa=p;}
	if(mid<b){tree[p].r=build(mid+1,b);tree[tree[p].r].fa=p;}
	update(p);
	return p;
}
inline void del(int p){
	splay(p,0);que[++cnt]=tree[p].pre,aque[tree[p].pre]=cnt;
	int pl=tree[p].l,pr=tree[p].r;
	if(pl&&pr){
		while(tree[pl].r)pushdown(pl),pl=tree[pl].r;
		while(tree[pr].l)pushdown(pr),pr=tree[pr].l;
		splay(pl,0);splay(pr,pl);
		tree[pr].l=0;update(pr);new1(pr,1);update(pl);
	}else if(pl){
		while(tree[pl].r)pushdown(pl),pl=tree[pl].r;
		splay(pl,0);
		tree[pl].r=0;update(pl);
	}else if(pr){
		while(tree[pr].l)pushdown(pr),pr=tree[pr].l;
		splay(pr,0);
		tree[pr].l=0;update(pr);new1(pr,1);
	}else root=0;
}
inline int min1(int i,int j){return i<j?i:j;}
inline bool cmp1(qry i,qry j){return i.y>j.y;}
inline bool cmp2(qry i,qry j){return i.id<j.id;}
inline void add(int x,int f){
	if(!x)return;
	while(x<=cnt)tim[x]+=f,x+=x&(-x);
}
inline int sum(int x){
	int s=0;
	while(x>=1)s+=tim[x],x-=x&(-x);
	return s;
}
int main(){
	n=read(),q=read();
	for(int i=1;i<=n;++i){
		a1[i]=read(),delta[i]=i-a1[i];
		if(delta[i]<0)delta[i]=inf;
	}
	tree[0].mn=inf,num=cnt=0,root=build(1,n);
	memset(aque,0,sizeof aque);
	while(tree[root].mn==0)del(tree[root].pos);
	for(int i=0;i<=n;++i)apr[i]=cnt+1;
	for(int i=1;i<=cnt;++i)apr[que[i]]=i;
	for(int i=2;i<=n;++i)apr[i]=min1(apr[i],apr[i-1]);
	for(int i=1;i<=q;++i)q1[i].x=read(),q1[i].y=n-read()+1,q1[i].id=i;
	sort(q1+1,q1+q+1,cmp1);memset(tim,0,sizeof tim);
	q1[0].y=n+1;
	for(int i=1;i<=cnt;++i)add(i,1);
	for(int i=1;i<=q;++i){
		for(int j=q1[i-1].y-1;j>=q1[i].y;--j)add(aque[j],-1);
		q1[i].ans=sum(apr[q1[i].x]-1);
	}
	sort(q1+1,q1+1+q,cmp2);
	for(int i=1;i<=q;++i)printf("%d\n",q1[i].ans);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值