1. 程式人生 > >[BZOJ 3110] K大數查詢

[BZOJ 3110] K大數查詢

msu amp tex bsp -c date() ron 不用 color

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位機

[BZOJ 3110] K大數查詢