【XSY2720】区间第k小 整体二分 可持久化线段树

题目描述

  给你你个序列,每次求区间第\(k\)小的数。

  本题中,如果一个数在询问区间中出现了超过\(w\)次,那么就把这个数视为\(n\)。

  强制在线。

  \(n\leq 100000,a_i<n,w\leq n\)

题解

  考虑整体二分。

  先看看离线要怎么做。

  现在我们要计算每个数对每个区间的贡献。

  对于每个询问区间和每种数,让这个区间最右边\(w\)个数对这个询问的贡献为\(1\),第\(w+1\)个数对这个询问的贡献为\(-w\)。

  这样每个数的贡献就是二维平面上的一个矩形。可以用扫描线+线段树解决。

  时间复杂度:\(O(n\log n)\)

  但问题是强制在线。

  可以把这棵线段树可持久化。

  时间复杂度不变。

  总时间复杂度:\(O(n\log^2 n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
#include<utility>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
namespace sgt
{
    struct node
    {
        int ls,rs,v;
    };
    node a[50000010];
    int cnt;
    int insert(int p1,int l,int r,int v,int L,int R)
    {
        int p=++cnt;
        a[p]=a[p1];
        if(l<=L&&r>=R)
        {
            a[p].v+=v;
            return p;
        }
        int mid=(L+R)>>1;
        if(l<=mid)
            a[p].ls=insert(a[p].ls,l,r,v,L,mid);
        if(r>mid)
            a[p].rs=insert(a[p].rs,l,r,v,mid+1,R);
        return p;
    }
    int query(int p,int x,int L,int R)
    {
        if(L==R)
            return a[p].v;
        int s=a[p].v;
        int mid=(L+R)>>1;
        if(x<=mid)
            s+=query(a[p].ls,x,L,mid);
        else
            s+=query(a[p].rs,x,mid+1,R);
        return s;
    }
}
struct change
{
    int x,y1,y2,k,w;
    change(){}
    change(int a,int c,int d,int e,int f)
    {
        x=a;
        y1=c;
        y2=d;
        k=e;
        w=f;
//      printf("%d %d %d %d %d\n",x,y1,y2,k,w);
    }
};
int cmp(change a,change b)
{
    return a.x>b.x;
}
change c[1000010],c2[1000010];
int cnt;
int n,w,q,type;
int a[100010];
set<int> st[100010];
int rtcnt=0;
int ls[3000010];
int rs[3000010];
int crt;
vector<pii> d[3000010];
int build(int l,int r,int vl,int vr)
{
    if(vl==vr)
        return 0;
    int rr=++rtcnt;
    d[rr].push_back(pii());
    int now=0;
    int i;
    int vm=(vl+vr)>>1;
    int num=0;
    int cnt1=0;
    for(i=l;i<=r;i++)
    {
        if(i!=l&&c[i].x!=c[i-1].x&&num)
        {
            d[rr].push_back(pii(c[i-1].x,now));
            num=0;
        }
        if(c[i].k<=vm)
        {
            now=sgt::insert(now,c[i].y1,c[i].y2,c[i].w,1,n);
            num++;
            cnt1++;
        }
    }
    if(num)
        d[rr].push_back(pii(c[r].x,now));
    int l1=l,r1=l+cnt1;
    for(i=l;i<=r;i++)
        if(c[i].k<=vm)
            c2[l1++]=c[i];
        else
            c2[r1++]=c[i];
    for(i=l;i<=r;i++)
        c[i]=c2[i];
    ls[rr]=build(l,l+cnt1-1,vl,vm);
    rs[rr]=build(l+cnt1,r,vm+1,vr);
    return rr;
}
int get(vector<pii> &s,int x)
{
    if(s.size()==1)
        return 0;
    if(x>s[1].first)
        return 0;
    int l=1,r=s.size()-1;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(x>s[mid].first)
            r=mid-1;
        else
            l=mid;
    }
    return l;
}
int query(int rr,int l,int r,int k,int vl,int vr)
{
    if(vl==vr)
        return vl;
    int p=get(d[rr],l);
    int rt=d[rr][p].second;
    int s=sgt::query(rt,r,1,n);
    int vm=(vl+vr)>>1;
    if(k<=s)
        return query(ls[rr],l,r,k,vl,vm);
    else
        return query(rs[rr],l,r,k-s,vm+1,vr);
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    scanf("%d%d%d%d",&n,&w,&q,&type);
    int x,i;
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(i=n;i>=1;i--)
    {
        x=a[i];
        st[x].insert(i);
        int ed2=n;
        if(st[x].size()>=w+1)
        {
            int ed=n;
            if(st[x].size()>=w+2)
            {
                set<int>::iterator p=st[x].end();
                p--;
                ed=*p-1;
                st[x].erase(p);
            }
            set<int>::iterator p=st[x].end();
            p--;
            c[++cnt]=change(i,*p,ed,x,-w);
            ed2=*p-1;
        }
        c[++cnt]=change(i,i,ed2,x,1);
    }
    sort(c+1,c+cnt+1,cmp);
    int crt=build(1,cnt,0,n);
    int l,r,k;
    int last=0;
    for(i=1;i<=q;i++)
    {
        scanf("%d%d%d",&l,&r,&k);
        if(type)
        {
            l^=last;
            r^=last;
            k^=last;
        }
        last=query(crt,l,r,k,0,n);
        printf("%d\n",last);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/ywwyww/p/8513541.html

时间: 2024-08-28 02:06:33

【XSY2720】区间第k小 整体二分 可持久化线段树的相关文章

静态区间第k小 - 整体二分

蒟蒻终于学会整体二分啦! 思路 实现 丑陋无比的代码 #include <bits/stdc++.h> using namespace std; const int N = 200005; int ar[N]; int lowbit(int t) { return t & (-t); } void add(int i, int v) { for (; i < N; ar[i] += v, i += lowbit(i)); } int sum(int i) { int s = 0;

[bzoj3065] 带插入区间第k小值 [重量平衡树套线段树]

题面 传送门 思路 发现强制在线了...... 本来可以树套树解决的问题,现在外层不能使用线段树了,拿什么替代呢? 我们需要一种支持单点插入.下套数据结构.数据结构上传合并复杂度最多单log,不能旋转的数据结构 这不是摆明了用重量平衡树吗? 我选了替罪羊树作为上层结构,下面套了一棵线段树,就做完了 查询的时候把替罪羊树上对应的log个区间提取出来,一起在底层权值线段树上二分即可 详见代码注释 Code #include<iostream> #include<cstdio> #inc

「CF484E」Sign on Fence「整体二分」「线段树」

题意 给定一个长度为\(n\)的正整数序列,第\(i\)个数为\(h_i\),\(m\)个询问,每次询问\((l, r, w)\),为\([l, r]\)所有长度为\(w\)的子区间最小值的最大值.(类似于一类特殊的直方图最大子矩形问题) \(1 \leq n, m \leq 10^5\) 题解 我们考虑二分答案,这样\(n\)个数变成\(01\),若\(h_i\geq mid\)则为\(0\),否则为\(1\) 每次就相当于查询存不存在长度为\(w\)的连续\(1\).用线段树维护. 这有个问

Codevs-4919 线段树练习4(区间加上一个值并求摸个区间整除k的数的个数,线段树+数组维护)

给你N个数,有两种操作 1:给区间[a,b]内的所有数都增加X 2:询问区间[a,b]能被7整除的个数 输入描述 Input Description 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,表示操作的个数. 接下来Q行每行若干个整数.如果第一个数是add,后接3个正整数a,b,X,表示在区间[a,b]内每个数增加X,如果是count,表示统计区间[a,b]能被7整除的个数 输出描述 Output Description 对于每个询问输出一行一个答案 样例输入 Sample

poj 2104主席树求区间第k小

POJ - 2104 题意:求区间第k小 思路:无修改主席树 AC代码: #include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set&

P3834 【模板】可持久化线段树 1(主席树) 整体二分

求区间第k大  整体二分模板提 // luogu-judger-enable-o2 #include<bits/stdc++.h> using namespace std; #define rep(i,a,b) for(int i=(a);i<=(b);i++) #define repp(i,a,b) for(int i=(a);i>=(b);--i) #define ll long long #define see(x) (cerr<<(#x)<<'='&

HDU 2665.Kth number 区间第K小

Kth number Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 11394    Accepted Submission(s): 3465 Problem Description Give you a sequence and ask you the kth big number of a inteval. Input The f

POJ2104 区间第k小

题意就是区间第k大…… 题解: 前段时间用主席树搞掉了…… 如今看到划分树,是在想来写一遍,结果18号对着学长的代码调了一上午连样例都没过,好桑心…… 今天在做NOI2010超级钢琴,忽然发现用划分树很直观,果断决定再战划分树 对着网上的c++代码抄了一遍,A了,可是这编程复杂度有点高,忽然又看见盾哥的代码 很简短,和我原先的代码差不多,他怎么能A了呢……(后来发现区间第k小,我却写的第k大……sb啊) 考虑到盾哥的程序用到了离散化,因此没有考虑存在两个数相等的情况,这样就使代码减少很多 我报着

少年,想学带修改主席树吗 | BZOJ1901 带修改区间第k小

少年,想学带修改主席树吗 | BZOJ1901 带修改区间第k小 有一道题(BZOJ 1901)是这样的:n个数,m个询问,询问有两种:修改某个数/询问区间第k小. 不带修改的区间第k小用主席树很好写,不会的同学可以看一下这个. 加上修改怎么做呢?我们可以用数学老师成天讲的类比思想: 可以发现,不修改的区间k小问题中,每加入一个原序列中的数,对应的主席树在上一个的基础上进行修改,而查询的时候用右端点主席树减去左端点左边的主席树.这样的操作就像是维护前缀和:每次加入一个元素的时候,sum[i] =