Time:2016.05.09
Author:xiaoyimi
转载注明出处谢谢
传送门1
传送门2
思路:
之前没怎么接触过权值线段树(非主席树),这次就当学习了一下吧。一开始还把题意理解错了,我的天啊……
起初思考了好久,发现不知道怎么处理负数的情况,不过数据里并没有负数?
权值线段树的每个节点表示一个区间[L,R],存储原序列中权值为[L,R]的元素的信息,所以这里的权值线段树每个节点上都是一棵普通线段树,就是负责统计原序列中权值为[L,R]的元素的个数。
每次插入一个相同的值x时就相当于在权值线段树上从根节点一直向下到x所代表的叶子节点,每到权值线段树上的一个节点就修改这个节点所存储的普通线段树的信息
查询时利用二分思想即可(可参见主席树相关练习),但是这里是查询第K大,所以每次折半时计算右子树大小,K大于它就找左边,不然就找右边
注意:
1.代码中采用了标记永久化思想,压小了常数,不过这是我第一次学习使用这个特殊技巧,感觉有些生疏,以后还是要多加练习
(感觉网上对于这个技巧讲述的不多啊!要是那天我熟练了这个技巧了,来写一篇吧)
2.BZOJ上计算总和要无符号整型,不然会爆int,我的天啊……
代码:
#include<bits/stdc++.h> #define M 50005 #define ul unsigned int using namespace std; int n,m,cnt_S,cnt_V,root_S[M<<4],root_V; struct Segment_tree { ul sum,lazy; int ch[2]; }S[M*300]; struct Value_tree { int ch[2]; }V[M]; int in() { int t=0,f=1;char ch=getchar(); while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); } while (isdigit(ch)) t=(t<<3)+(t<<1)+ch-48,ch=getchar(); return f*t; } void S_change(int &rt,int begin,int end,int l,int r) { if (!rt) rt=++cnt_S; if (l<=begin&&end<=r) { S[rt].sum+=end-begin+1; S[rt].lazy++; return; } int mid=begin+end>>1; if (mid>=l) S_change(S[rt].ch[0],begin,mid,l,r); if (mid<r) S_change(S[rt].ch[1],mid+1,end,r); S[rt].sum=S[S[rt].ch[0]].sum+S[S[rt].ch[1]].sum+S[rt].lazy*(end-begin+1); } ul S_get(int &rt,int r) { if (!rt) return 0; if (l<=begin&&end<=r) return S[rt].sum; int mid=begin+end>>1;ul ans=S[rt].lazy*(min(r,end)-max(l,begin)+1); if (mid>=l) ans+=S_get(S[rt].ch[0],r); if (mid<r) ans+=S_get(S[rt].ch[1],r); return ans; } void V_change(int &rt,int x,int y,int z) { if (!rt) rt=++cnt_V; S_change(root_S[rt],1,n,x,y); if (begin==end) return; int mid=begin+end>>1; if (mid>=z) V_change(V[rt].ch[0],y,z); else V_change(V[rt].ch[1],z); } int V_get(int rt,int z) { if (begin==end) return end; int mid=begin+end>>1; ul t=S_get(root_S[V[rt].ch[1]],y); if (z<=t) return V_get(V[rt].ch[1],z); else return V_get(V[rt].ch[0],z-t); } main() { n=in();m=in(); int opt,z; while (m--) { opt=in();x=in(); y=in();z=in(); if (opt==1) V_change(root_V,z); else printf("%lu\n",V_get(root_V,z)); } }