【划分树】
别人不会主席树而发明的--------划分树
·前面的言
如果你要求区间的最大值的话可以选择我们的划分树,但是不好意思,我们不提供修改。
·区间第k大
已知含有N个正整数的数列 x0, x1 ... xN-1。对于每个询问的区间[l, r](包含左右端点),在区间内找出一个数x使得尽量小。
我们可以冷静地分析出应该找一个区间排序后中间的数来作为我们选取的数,那难道我们每次都很慌张地sort吗?看划分树是怎样做的吧。
·划分树的构建
如果你已经发现了图中的规律。我们就可以定义一个结构体tree[]{val[],num[],(sum[])}; 用tree[i].val[j]来表示在上图中第i层第j个位置的值,和第i层第j个位置到红色隔板之间(包括端点的)有多少个到了左边(即绿色边的个数)。这样可以算出到右边的个数(红边),再定义sum,应用于此题求解答案。
抽象(耿直)的说,我们将一个区间递归地排序,划分,记下每个位置到区间开头之间别排序到1-mid的个数。(注意理解) 另外就是我们要用same记下1-mid间和sort[mid]相等的数的个数,便于把它们划分到左边;
·划分树的查询
查询部分是有点困难的。基本思路和线段树类似我们要查询的内容是这个东东:
query(cur,l,r,ql,qr,k) cur为当前层数,l和r为待查询左右区间端点,ql和qr为我们当前要查询的左右区间端点(一会你会理解ql qr的存在是有必要的),k为第几大。如代码,用t2-t1算出ql到qr之间别分到1-mid的个数,如果够k的话,往左找;不然往右。l和r的转移比较好理解,主要是ql和qr还有k。基于这样的事实,分别被划分到两边的数的相对位置是不变的,并且k也可以用这种思想更新像这样:
计算出新的ql和qr(强烈建议看daima);
1 /*5 2 3 6 2 2 4 3 2 4 1 4 5 0 2 6 */ 7 #include <cstdio> 8 #include <iostream> 9 #include <cstring> 10 #include <algorithm> 11 #include <queue> 12 #include <vector> 13 #include <ctime> 14 #include <cmath> 15 #define inf 0x3f3f3f3f 16 #define ll long long 17 #define N 100010 18 #define mem(f,a) memset(f,a,sizeof(f)) 19 #define Run(i,l,r) for(ll i=l;i<=r;i++) 20 #define Don(i,l,r) for(ll i=l;i>=r;i--) 21 #define Eun(i,u,E) for(ll i=head[u],v=E[i].v;i!=-1;i=E[i].next,v=E[i].v) 22 using namespace std; 23 ll n,a[N],sorted[N]; 24 struct node{ 25 ll val[N],num[N]; 26 ll sum[N]; 27 }tree[20]; 28 ll sum[N]; 29 ll ans=0; 30 void Build(ll cur,ll l,ll r) 31 { if (l==r) return; 32 ll mid=(l+r)/2,same=mid-l+1,p=l,q=mid+1,sum=0,num=0; 33 Run(i,l,r) if (tree[cur].val[i]<sorted[mid]) same--; 34 Run(i,l,r){ 35 if (tree[cur].val[i]<sorted[mid]||tree[cur].val[i]==sorted[mid]&&same){ 36 tree[cur+1].val[p++]=tree[cur].val[i]; 37 num++; sum+=tree[cur].val[i]; 38 if (tree[cur].val[i]==sorted[mid]) same--; 39 } 40 else { 41 tree[cur+1].val[q++]=tree[cur].val[i]; 42 } 43 tree[cur].num[i]=num; 44 tree[cur].sum[i]=sum; 45 } 46 Build(cur+1,l,mid); 47 Build(cur+1,mid+1,r); 48 } 49 ll query(ll cur,ll l,ll r,ll ql,ll qr,ll k) 50 { if (l==r) return tree[cur].val[l]; 51 else { 52 ll mid=(l+r)/2; 53 ll t1=(l==ql) ? 0: tree[cur].num[ql-1],t2=tree[cur].num[qr]; 54 if (k<=t2-t1) return query(cur+1,l,mid,l+t1,l+t2-1,k); 55 else { 56 ans+=tree[cur].sum[qr]; 57 if (ql!=l) ans-=tree[cur].sum[ql-1]; 58 return query(cur+1,mid+1,r,mid+1+ql-l-t1,mid+1+qr-l-t2,k-t2+t1); 59 } 60 } 61 } 62 int main() 63 { freopen("MinimumSum.in","r",stdin); 64 freopen("MinimumSum.out","w",stdout); 65 scanf("%lld",&n); 66 Run(i,1,n){ 67 scanf("%lld",&a[i]); 68 } 69 Run(i,1,n) { 70 tree[0].val[i]=sorted[i]=a[i]; 71 tree[0].sum[i]=sum[i]=sum[i-1]+a[i]; 72 } 73 sort(sorted+1,sorted+n+1); 74 Build(0,1,n); 75 ll q,qi,qj; 76 scanf("%lld",&q); 77 Run(i,1,q){ 78 scanf("%lld%lld",&qi,&qj); 79 qi++; qj++; 80 ans=0; ll mid=(qj+qi)/2-qi+1; 81 ll x=query(0,1,n,qi,qj,mid); 82 ans=1ll*x*(mid-1)-ans+(sum[qj]-sum[qi-1]-ans-x)-1ll*x*(qj-qi+1-mid); 83 printf("%lld\n",ans); 84 } 85 return 0; 86 }//by tkys_Austin;
大米飘香的总结:
真的很ga,大米兔走了那么久我都没发过一篇,总是借口很忙。本文介绍了划分树的构建方式和其查询思想,建议读者运用不太精美的插图帮助理解。虽然和学长讲东西技巧还差的很多,但慢慢来。另外我发现很多东西不及时复习的话很快就忘的一干二净了。期末要停竞赛,回来时已会是一个月之后了,我得好好考,不然省选前老师又不让我来集训。曾经让那么多届OIer恐惧的东西,就要降临在我身上了————省选。。。。加油!
我要高飞 到天空的顶点 我要飞到无人能及的视界
飞得再累 不准自己跌坠 直到太阳就在我的指尖
-----------张杰《高飞》

浙公网安备 33010602011771号