可持久化线段树-主席树
可持久化线段树不是一棵完全二叉树,不能用层次序编号,直接记录每个节点的左右子节点,与trie的字符子针类似
可持久化线段树的空间复杂度为O(N+MlogN).
一般L,R,sum,num至少开25倍空间
结构图:(摘自<<算法竞赛进阶指南>>)
模板题
https://vjudge.net/problem/POJ-2104
题意:
询问区间第k小,不修改
每次查询就查询左子树的个数是否大于k
如果大于,呢么第k大肯定在左子树
否则去右子树查找,最后找到的(l==r),呢个坐标就是答案了,返回b[l].
ac:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];//原结点值(会离散化),离散化前的值,第i次的根
int L[MAXN],R[MAXN];//L[i]为第i个节点的左儿子,R[i]为第i的节点的右儿子
int sum[MAXN];//sum[i]为节点的所代表的区间的总个数,即i节点的值
int tot=0;
int build(int l,int r)//建立空树
{
int now=++tot;
sum[now]=0;
if(l!=r)//不为叶子结点,继续递归
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
//每次插入一个点,即加入一条链(长度logn)
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;//新结点的个数为旧结点个数+1
L[now]=L[pre],R[now]=R[pre];//新结点继承旧结点
if(l!=r)//创建一条链
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
int query(int x,int y,int l,int r,int k)
{
if(l==r)
return b[l];//返回l,l已经离散化,所以返回离散化后的b[l]
int mid=(l+r)>>1;
int num=sum[L[y]]-sum[L[x]];//左子树的个数
if(num>=k)
return query(L[x],L[y],l,mid,k);
else
return query(R[x],R[y],mid+1,r,k-num);//去右子树找第k-num(左子树个数)
}
int main()
{
int n,m,l,r,k;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+1+n);
int num=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+num+1,a[i])-b;
root[0]=build(1,num);//建立一棵空树
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,num,a[i]);//root[i]是在root[i-1]的基础上建立的
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",query(root[l-1],root[r],1,num,k));
}
return 0;
}
题意:
询问区间l~r中小于等于k的数目
解析:
查询<=k的,我们二分出第一个大于等于k的,如果他不等于k,就--,
如果是==0,就直接输出0,不能取搜0,因为最小是1会出现非法访问.
ac:
#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0;
int build(int l,int r)
{
int now=++tot;
sum[now]=0;
if(l!=r)
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;
L[now]=L[pre],R[now]=R[pre];
if(l!=r)
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
int query(int x,int y,int l,int r,int k)
{
if(sum[y]-sum[x]==0) return 0;
if(l==r)
return sum[y]-sum[x];
int mid=(l+r)>>1;
int res=sum[L[y]]-sum[L[x]];
if(k<=mid)
query(L[x],L[y],l,mid,k);
else
return res+query(R[x],R[y],mid+1,r,k);
}
void init()
{
tot=0;
}
int getnum(int num,int l,int r,int k)//查询小于等于k的数的个数
{
int kk=lower_bound(b+1,b+num+1,k)-b;
if(b[kk]!=k)
kk--;
if(kk==0)
return 0;
else
return query(root[l-1],root[r],1,num,kk);
}
int main()
{
int t,n,m,l,r,k,cas=1;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
int num=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+num+1,a[i])-b;
root[0]=build(1,num);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,num,a[i]);
printf("Case %d:\n",cas++);
while(m--)
{
scanf("%d%d%d",&l,&r,&k);
l++,r++;
printf("%d\n",getnum(num,l,r,k));
}
}
return 0;
}
hdu:6621
给定一个数组,有m次询问,每次询问l,r,p,k
每次询问区间(l,r)内,与p的差距第k小的数 与p的差的绝对值(包括p)
解析:
二分差值v
查询(p-v-1,p+v)中的数目,数目大于等于k的情况下,求最小的v
ac:
#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0;
int build(int l,int r)
{
int now=++tot;
sum[now]=0;
if(l!=r)
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;
L[now]=L[pre],R[now]=R[pre];
if(l!=r)
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
int query(int x,int y,int l,int r,int k)
{
if(sum[y]-sum[x]==0) return 0;
if(l==r)
return sum[y]-sum[x];
int mid=(l+r)>>1;
int res=sum[L[y]]-sum[L[x]];
if(k<=mid)
query(L[x],L[y],l,mid,k);
else
return res+query(R[x],R[y],mid+1,r,k);
}
void init()
{
tot=0;
}
int getnum(int num,int l,int r,int k)//查询小于等于k的数的个数
{
int kk=lower_bound(b+1,b+num+1,k)-b;
if(b[kk]!=k)
kk--;
if(kk==0)
return 0;
else
return query(root[l-1],root[r],1,num,kk);
}
int main()
{
int t,n,m,l,r,k;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
int maxs=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i],maxs=max(a[i],maxs);
sort(b+1,b+n+1);
int num=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+num+1,a[i])-b;
root[0]=build(1,num);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,num,a[i]);
int res=0,p;
while(m--)
{
scanf("%d%d%d%d",&l,&r,&p,&k);
l=l^res,r=r^res,p=p^res,k=k^res;
int L=0,R=maxs;
int ans=0;
while(L<=R)
{
int mid=(L+R)>>1;
int kk=getnum(num,l,r,p+mid)-getnum(num,l,r,p-mid-1);//查询(p-mid-1,p+mid)范围内的数的个数
if(kk>=k)
{
R=mid-1;
ans=mid;
}
else{
L=mid+1;
}
}
printf("%d\n",ans);
res=ans;
}
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=6601
题意:给定一个木棍长度区间,让你在(l,r)区间里用三个木棍组成周长最大的三角形
解析:第k大+从大到小遍历选取最大的,要求边长a<b+c,如果没有-1,(r-l+1)<=2特判,注意可能需要swap(l,r)
https://vjudge.net/problem/HDU-6703
题意:
强制在线,有两种操作:
1.删除数a[x]
2.求在区间(1,r)中未出现,且>=k的最小的数
解析:
主席树(离线)+set
每次删除了数,直接加入到set中,删除的数,肯定不在(1,r)中,将在线转化为离线
主席树求区间(r+1,n+1)的第一个>=k的值,求set中第一个>=k的值
ac:
#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int L[MAXN],R[MAXN],sum[MAXN];
int root[MAXN],a[MAXN],b[MAXN];
int tot;
void init()
{
tot=0;
}
int build(int l,int r)
{
int now=++tot;
sum[now]=0;
if(l!=r)
{
int m=(l+r)>>1;
L[now]=build(l,m);
R[now]=build(m+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+1;
L[now]=L[pre],R[now]=R[pre];
if(l!=r)
{
int m=(l+r)>>1;
if(k<=m)
L[now]=update(L[pre],l,m,k);
else
R[now]=update(R[pre],m+1,r,k);
}
return now;
}
int query(int pre,int now,int l,int r,int k)
{
if(sum[now]-sum[pre]==0)//(l,r)范围内的数没有
return 0;
if(l==r)
return l;
int m=(l+r)>>1;
if(k>m) //k>m,只可能在右子树
return query(R[pre],R[now],m+1,r,k);
int ans=query(L[pre],L[now],l,m,k);
if(ans)
return ans;
else
return query(R[pre],R[now],m+1,r,k);//只能去右子树
}
set<int> st;
set<int>::iterator it;
int main()
{
int t,n,m;
scanf("%d",&t);
while(t--)
{
st.clear();
init();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
a[n+1]=n+1;
n=n+1;
root[0]=build(1,n);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,n,a[i]);
int op,res=0,x,r,k;
while(m--)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d",&x);
x=x^res;
st.insert(a[x]);
}
else{
scanf("%d%d",&r,&k);
r=r^res,k=k^res;
int ans=query(root[r+1-1],root[n],1,n,k);
it=st.lower_bound(k);
if(it!=st.end())
ans=min(ans,*it);
printf("%d\n",ans);
res=ans;
}
}
}
return 0;
}
https://ac.nowcoder.com/acm/contest/889/H
题意:
给定n棵竹子,高度位a[i]
m次询问
每次选择第L~第R棵竹子,分y次将所以竹子砍完,每次砍掉的竹子长度相同,没一次同时看这个区间的所以竹子,问第x次砍在什么位置
解析:
先前缀和a[i],第x次剩下的高度和=(sum[r]-sum[l-1])*(1-x/y).
查询最大高度多少时,高度和=(sum[r]-sum[l-1])*(1-x/y).sumh=sum(高度小于mid)+高度大于mid的数目*mid
建立主席树,二分最大高度,数目与值和结合处理应用
ac:
#include<bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;
int L[MAXN*24],R[MAXN*24];
ll sum[MAXN*24],num[MAXN*24],h[MAXN*2];
int root[MAXN*2];
int tot=0;
int build(int l,int r)
{
int now=++tot;
sum[now]=num[now]=0;
if(l!=r)
{
int mid=(l+r)>>1;
L[now]=build(l,mid);
R[now]=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int k)
{
int now=++tot;
sum[now]=sum[pre]+k;num[now]=num[pre]+1;
L[now]=L[pre];R[now]=R[pre];
if(l!=r)
{
int mid=(l+r)>>1;
if(k<=mid)
L[now]=update(L[pre],l,mid,k);
else
R[now]=update(R[pre],mid+1,r,k);
}
return now;
}
double query(int now,int pre,int l,int r,double need,int kk)
{
if(l==r)
{
return need/kk;//比l高的竹子的数目kk
}
int mid=(l+r)>>1;//二分砍的高度,sumax为砍后的竹子剩余高度和
double sumax=sum[L[now]]-sum[L[pre]]+1LL*mid*(kk-(num[L[now]]-num[L[pre]]));//左子树的和(比mid矮的树的和)+比mid高的树全变为mid高度
if(sumax>=need) //砍少了,继续砍
return query(L[now],L[pre],l,mid,need,kk);
else{ //砍多了,少砍点
return query(R[now],R[pre],mid+1,r,need-(sum[L[now]]-sum[L[pre]]),kk-(num[L[now]]-num[L[pre]]));
}
}
int main()
{
int n,q,xx,l,r;
double x,y;
scanf("%d%d",&n,&q);
root[0]=build(1,100000);
for(int i=1;i<=n;i++)
{
scanf("%d",&xx);
h[i]=h[i-1]+xx;
root[i]=update(root[i-1],1,100000,xx);
}
while(q--)
{
scanf("%d%d%lf%lf",&l,&r,&x,&y);
double need=(1.0-x/y)*(h[r]-h[l-1]);
printf("%.10f\n",query(root[r],root[l-1],1,100000,need,r-l+1));
}
return 0;
}