主席树
全称是可持久化权值线段树(以前一直分不清可持久化线段树和主席树的区别)
但实际上写法跟可持久化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;
}