[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位机

posted @ 2018-05-25 11:04  NewErA  阅读(223)  评论(0编辑  收藏  举报