[BZOJ 3110] K大数查询
Link:https://www.lydsy.com/JudgeOnline/problem.php?id=3110
Solution:
一道树套树的题,外层是权值线段树,里层是区间线段树。
对于权值线段树的节点 u 表示权值区间 [l, r),其对应的区间线段树的节点 v 表示序列 [l1, r1)中一共有多少个在权值区间 [l, r) 的数。
查询:要查 [a, b]的第 k 大,只要每次判断是向右子节点还是左子节点走即可
如果权值为[l,mid]中[a,b]区间的个数大于k,则说明在[l,mid]中,否则在[mid+1,r]中
修改:使用标记永久化,尽量不用PushDown
剩下的问题就是空间,理论上需要 O(n^2)的空间,我们可以对区间线段树动态开点
好像整体二分也能做?待填
Code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=5e4+10,MAXM=MAXN*20*20; ll sum[MAXM],lazy[MAXM]; int n,m,a,b,c,cnt,root[MAXN*4],lc[MAXM],rc[MAXM]; ll Query(int cur,int l,int r) { if (a<=l && r<=b) return sum[cur]; ll t1=0,t2=0;int mid=(l+r)>>1; if (a<=mid) t1=Query(lc[cur],l,mid); if (mid<b) t2=Query(rc[cur],mid+1,r); return (t1+t2)+1ll*(min(r,b)-max(a,l)+1)*lazy[cur]; } int query() { int l=1,r=n,cur=1; while(l!=r) { int mid=(l+r)/2; ll t=0; if ((t=Query(root[cur<<1],1,n))>=c) r=mid,cur<<=1; else l=mid+1,cur=(cur<<1)+1,c-=t; } return l; } void modify(int &cur,int l,int r) { if (!cur) cur=++cnt; if (a<=l && r<=b) { sum[cur]+=r-l+1; lazy[cur]++; return; } int mid=(l+r)/2; if (a<=mid) modify(lc[cur],l,mid); if (mid<b) modify(rc[cur],mid + 1,r); sum[cur]=sum[lc[cur]]+sum[rc[cur]]+lazy[cur]*(r-l+1); } void update() { int l=1,r=n,cur=1; while(l!=r) { int mid=(l+r)/2; modify(root[cur],1,n); if (mid<c) l=mid+1,cur=(cur<<1)+1; else r=mid,cur<<=1; } modify(root[cur],1,n); } int main() { scanf("%d%d",&n,&m); while (m--) { int op; scanf("%d%d%d%d",&op,&a,&b,&c); if (op==1) c=n-c+1,update(); else printf("%d\n",n-query()+1); } }
Review:
1、可以通过c=n+1-c的方式将 求第K大 -----> 求第K小
2、注意开long long,此题会爆int
3、能用int尽量用int,long long在32位机上的计算是int的几倍耗时
OI使用32位机