题目链接:
前置知识:
无旋treap和线段树
无旋treap推荐博客:https://www.luogu.com.cn/blog/85514/fhq-treap-xue-xi-bi-ji
线段树推荐博客:https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html
题目大意:
一共有n个位置,每个位置上都有一些方块。每次有两种操作:
1 x 询问位置x有多少个方块
2 x y 将位置x y的方块向左移一格,并问你有多少个方块被推动
所有方块会受力学的影响,即推动时,下方没有方块时会下降。题解:
牛客多校10有一道差不多的题,不过那个题是个签到题,而这个题就不是这么好写了,而且还不好调!!!!
首先考虑怎么能够维护这两个操作,并且时间复杂度要是O(nlogn)。
比赛时一眼想到了可以用splay或者fhq treap来维护,但是因为时间原因就没来的及写。。。
我们可以用排名来表示位置,权值来表示方块个数,这样看起来就可以套无旋 treap的模板了。。。
第一个操作比较好处理,就是查询排名为x的方块数。
对于第二个操作我们可以这样维护:
假设第一个比高度yy小的位置为pos
那么这个操作就等同于以下操作:
①把pos位置的高度 加上 (pos+1的高度减去(yy-1))
②把x位置的高度 变成 (yy-1)
③把[pos+2,x]的高度平移到[pos+1,x-1]
怎么用无旋treap维护呢?
我们可以先求出pos位置和pos+1位置,并同时分割出[1,pos-1]区间和[pos+2,x]区间.
然后我们就可以直接合并[1,pos-1]区间和[pos-2,x]区间,这一步就实现了平移的操作(即③操作)
最后直接插入pos位置的高度和x位置的高度(即①操作和②操作)就行了。
因此我们就需要按权值分裂和按排名分裂,以及合并三个基础操作。
而且按权值分裂还需要稍微修改一下。
代码如下:
#include<bits/stdc++.h> #define rp(i,s,t) for(int i=(s);i<=(t);i++) using namespace std; typedef long long ll; inline int read(){ int s=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; } mt19937 rnd(time(0)); const int INF = 0x3f3f3f3f; const int N = 600000 + 5; struct Tree{ int ls,rs; int sz,rnd,val; int min; ll sum; }tree[N]; int cnt; int x,y,z,root; int l,r,l1,r1,l2,r2; inline void init(){ cnt=root=0; memset(tree,0,sizeof tree); tree[0].min=INF; tree[0].sum=0; } inline void pushup(int now){//线段树上传操作 tree[now].sz=tree[tree[now].ls].sz+tree[tree[now].rs].sz+1; tree[now].min=min(min(tree[tree[now].ls].min,tree[tree[now].rs].min),tree[now].val); tree[now].sum=tree[tree[now].ls].sum+tree[tree[now].rs].sum+tree[now].val; } inline void split_val(int now,int v,int &x,int &y){ if(!now) { x=y=0; return ; } if(tree[now].val<=v||tree[tree[now].rs].min<=v)//注意当右儿子的最小值小于等于v时,也可以把当前点以及左子树加到左区间虚拟树 x=now,split_val(tree[now].rs,v,tree[now].rs,y); else y=now,split_val(tree[now].ls,v,x,tree[now].ls); pushup(now); } inline void split_rank(int now,int k,int &x,int &y){ if(!now){ x=y=0; return ; } if(k<=tree[tree[now].ls].sz) y=now,split_rank(tree[now].ls,k,x,tree[now].ls); else x=now,split_rank(tree[now].rs,k-tree[tree[now].ls].sz-1,tree[now].rs,y); pushup(now); } inline int merge(int u,int v){ if(!u||!v)return u|v;//如果有任意一个树为空就返回非空的一棵的根 if(tree[u].rnd<tree[v].rnd){//如果u为当前根 tree[u].rs=merge(tree[u].rs,v);//继续合并 pushup(u);//上传 return u;//返回根 } else{//如果v为当前根 tree[v].ls=merge(u,tree[v].ls); pushup(v); return v; } } inline int new_node(int a){//新建节点 tree[++cnt].sz=1; tree[cnt].val=tree[cnt].min=tree[cnt].sum=a;tree[cnt].rnd=rand(); return cnt;//此时的cnt代表的是新建节点 } inline void ins(int p,int a){//插入值为a,排名为p的节点 split_rank(root,p-1,x,y); root=merge(merge(x,new_node(a)),y); } inline int kth(int k){//求第k排名的值 split_rank(root,k,x,y); split_rank(x,k-1,l,r); int val=tree[r].val; root=merge(merge(l,r),y); return val; } inline ll calc(int xx,int yy){//计算答案,这道题排名等同与位置 /* 假设第一个比高度yy小的位置为pos 那么这个操作就等同于以下操作: ①把pos位置的高度 加上 (pos+1的高度减去(yy-1)) ②把x位置的高度 变成 (yy-1) ③把[pos+2,x]的高度平移到[pos+1,x-1] 怎么用无旋treap维护呢? 我们可以先求出pos位置和pos+1位置,并同时分割出[1,pos-1]区间和[pos+2,x]区间. 然后我们就可以直接合并[1,pos-1]区间和[pos-2,x]区间,这一步就实现了平移的操作(即③操作) 最后直接插入pos位置的高度和x位置的高度就行了。 */ if(kth(xx)<yy) return 0*1ll;//当第x排名的值小于yy时(即第x位置的高度小于yy) int ls,rs;split_rank(root,xx,ls,rs);//通过排名分裂把总区间分成排名为[1,x](即 ls)和[x+1,y](即 rs) if(tree[ls].min>=yy){//当分裂后的左区间树的最小值也大于等于yy,即[1,x]区间没有比yy小的 root=merge(ls,rs); return 0*1ll; } split_val(ls,yy-1,l,r);//通过权值排名再把[1,x]区间按照权值(即高度)分裂为[min,yy-1](即 l 子树)和[yy,max](即 r 子树) ll ans=tree[r].sum-1ll*tree[r].sz*(yy-1);//移动的方块树为r子树的方块和减去r子树的大小乘以(yy-1),这个不难想到 int pos=tree[l].sz;//pos用来第一个比高度yy小的位置 split_rank(r,1,l2,r2);//把r子树分裂成[pos+1,pos+1](l2)区间和[pos+2,x]区间(r2) split_rank(l,pos-1,l1,r1);//把r子树分裂成[1,pos-1](l1)区间和[pos,pos]区间(r1) tmp=tree[r1].val+tree[l2].val-yy+1;//预先求出①操作的pos位置的最终权值 root=merge(merge(l1,r2),rs);//对应上面的③操作 ins(pos,tmp);ins(xx,yy-1);//对应上面的①和②操作操作 return ans; } vector<int> v; void dfs(int now){ if(tree[now].ls) dfs(tree[now].ls); v.push_back(tree[now].val); if(tree[now].rs) dfs(tree[now].rs); } int main() { int T=read(); while(T--){ int n=read(),q=read(); init();v.clear(); rp(i,1,n){ int vv=read(); ins(i,vv); } while(q--){ int opt=read(); if(opt==1) { x=read(),y=read(); printf("%lld\n",calc(x,y)); } else{ x=read(); printf("%d\n",kth(x)); } } dfs(root); rp(i,0,n-1) printf("%d%c",v[i],i==n-1?'\n':' '); } return 0; } /* 1 8 4 2 1 1 4 4 6 2 3 1 6 4 2 5 1 1 1 1 8 2 */