主席树入门题

主席树

全称是可持久化权值线段树(以前一直分不清可持久化线段树和主席树的区别)
但实际上写法跟可持久化trie没什么区别,维护权值就OK了

那就不讲了

jzoj1011

题目

Description
JZ拥有一个很大的野生动物园。这个动物园坐落在一个狭长的山谷内,这个区域从南到北被划分成N个区域,每个区域都饲养着一头狮子。这些狮子从北到南编号为1,2,3,…,N。每头狮子都有一个觅食能力值Ai,Ai越小觅食能力越强。饲养员西西决定对狮子进行M次投喂,每次投喂都选择一个区间[I,J],从中选取觅食能力值第K强的狮子进行投喂。值得注意的是,西西不愿意对某些区域进行过多的投喂,他认为这样有悖公平。因此西西的投喂区间是互不包含的(即区间[1,10]不会与[3,4]或[5,10]同时存在,但可以与[9,11]或[10,20]一起)。同一区间也只会出现一次。你的任务就是算出每次投喂后,食物被哪头狮子吃掉了。

Input
觅食能力值。(1<=能力值<=maxlongint)。此后M行,每行描述一次投喂。第t+2的三个数I,J,K表示在第t次投喂中,西西选择了区间[I,J]内觅食能力值第K强的狮子进行投喂。
Output
输出文件有M行,每行一个整数。第i行的整数表示在第i次投喂中吃到食物的狮子的觅食能力值。

Sample Input
7 2
1 5 2 6 3 7 4
1 5 3
2 7 1
Sample Output
3
2

Data Constraint

Hint
对于100%的数据,有1<=N<=100000,1<=M<=50000。

题解

区间第K大……标准的主席树例题
维护每个范围内数的个数,然后[R]-[L-1],树上二分查找

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define Mx 2147483647
using namespace std;

int tr[5000001][3];
int n,i,j,k,l,r,Q,s,len;

void New(int t,int x)
{
    len++;
    tr[len][0]=tr[tr[t][x]][0]+1;
    tr[len][1]=tr[tr[t][x]][1];
    tr[len][2]=tr[tr[t][x]][2];
    tr[t][x]=len;
}

void change(int t,long long l,long long r,int x)
{
    int mid=(l+r)/2;

    if (l==r) return;

    if (x<=mid)
    {
        New(t,1);
        change(tr[t][1],l,mid,x);
    }
    else
    {
        New(t,2);
        change(tr[t][2],mid+1,r,x);
    }
}

void find(int t,int T,long long l,long long r,int s)
{
    int mid=(l+r)/2;

    if (l==r)
    {
        printf("%d\n",l);
        return;
    }

    if (tr[tr[T][1]][0]-tr[tr[t][1]][0]>=s)
    find(tr[t][1],tr[T][1],l,mid,s);
    else
    find(tr[t][2],tr[T][2],mid+1,r,s-(tr[tr[T][1]][0]-tr[tr[t][1]][0]));
}

int main()
{
    scanf("%d%d",&n,&Q);len=n;
    fo(i,1,n)
    {
        tr[i][0]=tr[i-1][0]+1;
        tr[i][1]=tr[i-1][1];
        tr[i][2]=tr[i-1][2];

        scanf("%d",&s);
        change(i,1,Mx,s);
    }

    for (;Q;Q--)
    {
        scanf("%d%d%d",&l,&r,&k);
        find(l-1,r,1,Mx,k);
    }

    return 0;
}

jzoj3547

题目

Description
有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

Input
第一行n,m。

第二行为n个数。

从第三行开始,每行一个询问l,r。
Output
一行一个数,表示每个询问的答案。

Sample Input
5 5

2 1 0 2 1

3 3

2 3

2 4

1 2

3 5
Sample Output
1

2

3

0

3

Data Constraint
对于30%的数据:

1<=n,m<=1000

对于100%的数据:

1<=n,m<=200000

0<=ai<=10^9

1<=l<=r<=n

题解

45%

维护每个范围内的数出现次数,之后二分+奇怪剪枝
如果左区间内的数出现次数<区间长度,那么就一定在左区间,否则都有可能
实际就是暴力

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(x,y) (x<y?x:y)
#define Mx 2147483647
using namespace std;

int tr[10000001][3];
int n,i,j,k,l,r,Q,s,len,ans;

void New(int t,int x)
{
    len++;
    tr[len][0]=tr[tr[t][x]][0]+1;
    tr[len][1]=tr[tr[t][x]][1];
    tr[len][2]=tr[tr[t][x]][2];
    tr[t][x]=len;
}

void change(int t,long long l,long long r,int x)
{
    int mid=(l+r)>>1;

    if (l==r) return;

    if (x<=mid)
    {
        New(t,1);
        change(tr[t][1],l,mid,x);
    }
    else
    {
        New(t,2);
        change(tr[t][2],mid+1,r,x);
    }
}

void find(int t,int T,long long l,long long r)
{
    int mid=(l+r)>>1;

    j++;

    if (l==r)
    {
        if (tr[T][0]==tr[t][0])
        ans=l;
        return;
    }

    find(tr[t][1],tr[T][1],l,mid);

    if (ans<123456789) return;

    if (tr[tr[T][1]][0]-tr[tr[t][1]][0]>=r-mid)
    find(tr[t][2],tr[T][2],mid+1,r);
}

int main()
{
    scanf("%d%d",&n,&Q);len=n;
    fo(i,1,n)
    {
        tr[i][0]=tr[i-1][0]+1;
        tr[i][1]=tr[i-1][1];
        tr[i][2]=tr[i-1][2];

        scanf("%d",&s);
        change(i,0,Mx,s);
    }

    for (;Q;Q--)
    {
        ans=123456789;

        j=0;
        scanf("%d%d",&l,&r);
        find(l-1,r,0,Mx);

        printf("%d\n",ans);
    }

    return 0;
}

100%

维护到当前位置时区间内的值出现的min(最右位置)
然后一个二分(从R开始),如果区间内所有数的最右位置都≥L以内就在右边,否则在左边
话说我还想过建10^9棵线段树

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(x,y) (x<y?x:y)
#define Mx 2147483647
using namespace std;

int tr[10000001][3];
int n,i,l,r,Q,s,len;

void New(int t,int x)
{
    len++;
    tr[len][0]=tr[tr[t][x]][0];
    tr[len][1]=tr[tr[t][x]][1];
    tr[len][2]=tr[tr[t][x]][2];
    tr[t][x]=len;
}

void change(int t,long long l,long long r,int x)
{
    int mid=(l+r)>>1;

    if (l==r)
    {
        tr[t][0]=i;
        return;
    }

    if (x<=mid)
    {
        New(t,1);
        change(tr[t][1],l,mid,x);
    }
    else
    {
        New(t,2);
        change(tr[t][2],mid+1,r,x);
    }

    tr[t][0]=min(tr[tr[t][1]][0],tr[tr[t][2]][0]);
}

void find(int t,long long l,long long r,int s)
{
    int mid=(l+r)>>1;

    if (l==r)
    {
        printf("%d\n",l);
        return;
    }

    if (tr[tr[t][1]][0]<s)
    find(tr[t][1],l,mid,s);
    else
    find(tr[t][2],mid+1,r,s);
}

int main()
{
    scanf("%d%d",&n,&Q);len=n;
    fo(i,1,n)
    {
        tr[i][0]=tr[i-1][0];
        tr[i][1]=tr[i-1][1];
        tr[i][2]=tr[i-1][2];

        scanf("%d",&s);
        change(i,0,Mx,s);
    }

    for (;Q;Q--)
    {
        scanf("%d%d",&l,&r);
        find(r,0,Mx,l);
    }

    return 0;
}

jzoj5710

题目

Description
在组合游戏中计算状态的 SG 值时,我们常常会遇到 mex 函数。mex(S) 的值为集合 S 中没有出现过的最小自然数。例如,mex({1,2}) = 0、mex({0,1,2,3}) = 4。
给定长度为 n 的序列 a。现有 m 次询问,每次给定 l 和 r,询问区间 [l,r] 的数构成的集合的 mex 值。

Input
输入数据的第一行包含三个整数 n、m 和 t,其中 t 为 0 或者 1,表示数据类型。
接下来一行,包含 n 个非负整数,为序列 a。
接下来 m 行,每行描述一个询问。第 i 行包含两个正整数 l 和 r,代表第 i 次询问的区间的左右端点。如果 t = 1,则询问进行了加密,从第二个询问开始,读入的 l 和 r 异或前一次询问的答案才是真正的询问左右端点。
Output
对于每个询问,输出一行,代表询问区间的 mex 值。

Sample Input
5 4 0
2 1 0 2 1
3 3
2 3
2 4
1 2
Sample Output
1
2
3
0

题解

加了个强制在线……但是没卵用

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(x,y) (x<y?x:y)
#define Mx 2147483647
using namespace std;

int tr[10000001][3];
int n,i,l,r,Q,s,len,size,ans;

void New(int t,int x)
{
    len++;
    tr[len][0]=tr[tr[t][x]][0];
    tr[len][1]=tr[tr[t][x]][1];
    tr[len][2]=tr[tr[t][x]][2];
    tr[t][x]=len;
}

void change(int t,long long l,long long r,int x)
{
    int mid=(l+r)>>1;

    if (l==r)
    {
        tr[t][0]=i;
        return;
    }

    if (x<=mid)
    {
        New(t,1);
        change(tr[t][1],l,mid,x);
    }
    else
    {
        New(t,2);
        change(tr[t][2],mid+1,r,x);
    }

    tr[t][0]=min(tr[tr[t][1]][0],tr[tr[t][2]][0]);
}

void find(int t,long long l,long long r,int s)
{
    int mid=(l+r)>>1;

    if (l==r)
    {
        ans=l;
        printf("%d\n",l);
        return;
    }

    if (tr[tr[t][1]][0]<s)
    find(tr[t][1],l,mid,s);
    else
    find(tr[t][2],mid+1,r,s);
}

int main()
{
    freopen("mex.in","r",stdin);
    freopen("mex.out","w",stdout);

    scanf("%d%d%d",&n,&Q,&size);len=n;
    fo(i,1,n)
    {
        tr[i][0]=tr[i-1][0];
        tr[i][1]=tr[i-1][1];
        tr[i][2]=tr[i-1][2];

        scanf("%d",&s);
        change(i,0,Mx,s);
    }

    for (;Q;Q--)
    {
        scanf("%d%d",&l,&r);
        l^=ans*size;
        r^=ans*size;

        find(r,0,Mx,l);
    }

    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值