主席树小结
https://zybuluo.com/ysner/note/1099145
标签(空格分隔): 主席树
前置技能
- 线段树
- 动态开点
- 标记永久化
- 离散化
定义
主席树=可持久化线段树=函数式线段树
线段树经过了若干次修改之后,仍然能找到原来某次修改前的线段树的信息的一种数据结构
建立
据说最无脑的方法是每修改一次新建一颗主席树???
单点修改:
- 线段树单点修改只会改变\(log\)个点(一条链),主席树同理。
- 那为什么要新建一颗主席树呢?新建修改过的链,再同原来的点接起来,这效果不是一样的吗?
区间修改:
- 如果像单点修改一样新建修改过的点,显然不好也不能\(Pushdown\)
- 这个问题可以用标记永久化解决
使用
1、静态整体Kth
滑稽吧...sort一遍就好了。
时间复杂度\(O(nlogn)\) 空间复杂度\(O(n)\)
2、动态整体Kth
离散化后开一棵权值线段树,每个位置的值表示这个位置对应的那个数(离散化后的)有多少个,向上维护和;
查询时先查询左子树和sum,比较k和sum的大小:若k<=sum则说明第k小数在左子树中,递归查询左子树;
否则,这个数对应的就是右子树中第k-sum小的数,k-=sum,递归查询右子树。
时间复杂度\(O(nlogn)\) 空间复杂度\(O(n)\)
3、静态区间Kth
对每个点以其前缀开一棵权值线段树,那么任意一段区间均可以表示成为两棵权值线段树作差,即R位置的线段树减去L-1位置上的线段树
每个点开一棵线段树空间复杂度\(O(n^2)\),MLE,考虑到后一个位置相比于前一个位置的更改只有\(logn\)个节点,所以使用主席树
时间复杂度\(O(nlogn)\) 空间复杂度\(O(nlogn)\)
4、动态区间Kth (不会实现!!!)
还是要想办法维护前缀和。如果只是同3、的前缀和的话,就要对前缀和进行\(O(nlogn)\)的单次修改,显然TLE。
这里考虑用树状数组维护前缀和。修改时,可以只修改logn个位置,复杂度\(O(log^2n)\);
查询时,依旧是R位置减去L-1位置,这时候不再是两棵线段树作差,而是log棵线段树与log棵线段树作差;跳的时候,log个节点一起跳到左子树/右子树
时间复杂度\(O(nlog^2n)\) 空间复杂度\(O(nlog^2n)\)
例题
T1
Luogu3834 【模板】可持久化线段树 1(主席树)
求静态区间第K小
查询区间[l,r]第k小?
可以开一棵值域线段树,维护数的个数
把数字离散化之后丢进线段树中去
然后直接在线段树上二分数字
如果k大于当前点左子树的点的个数,那么k减去这个个数,去右子树
否则去左子树
已知第i棵线段树保存前i个数字
那么运用前缀和的方法,把第r棵线段树和第l−1棵线段树作差,得到的就是这个区间内的数。
T2
Luogu3567 [POI2014]KUR-Couriers
每次询问一个区间内有没有一个数出现次数超过一半
一个区间内只会有一个数满足要求
主席树维护数的个数(即sort+unqiue+lower_bound)
L−1和R值域线段树做差,直接树上二分查询
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100;
int tot,L,R,rt[N],n,m;
struct seg{int ls,rs,num;}t[N*30];
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void Build(re int &x,re int l,re int r)
{
x=++tot;
if(l>=r) return;
re int mid=l+r>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
t[++tot]=t[x];t[x=tot].num++;
if(l>=r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos);
else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r)
{
if(l>=r) return l;
re int mx=max(t[t[B].ls].num-t[t[A].ls].num,t[t[B].rs].num-t[t[A].rs].num);
if(mx*2<=(R-L+1)) return 0;
re int mid=l+r>>1;
if(t[t[B].ls].num-t[t[A].ls].num==mx) return Query(t[A].ls,t[B].ls,l,mid);
else return Query(t[A].rs,t[B].rs,mid+1,r);
}
int main()
{
n=gi();m=gi();
Build(rt[0],1,n);
fp(i,1,n)
{
re int x=gi();rt[i]=rt[i-1];
Modify(rt[i],1,n,x);
}
while(m--)
{
L=gi(),R=gi();
printf("%d\n",Query(rt[L-1],rt[R],1,n));
}
return 0;
}
T3
Luogu3168 [CQOI2015]任务查询系统
有m个有优先级的任务,在时间区间[l,r]内被计算机运行
n个询问,求第x秒正在运行的前k小的任务优先级之和
把一个任务拆成两个,在l秒时加入,在r+1时减去
然后每个时间开值域的主席树就好了
// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e5+100;
int tot,L,R,rt[N],id[N],len,ans=1,n,m;
struct seg{int ls,rs,num,sum;}t[N*50];
struct tas
{
int t,w,f;
bool operator < (const tas &o) const {return t<o.t;}
}a[N<<1];
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void Modify(re int &x,re int l,re int r,re int pos,re int k)
{
t[++tot]=t[x];x=tot;
t[x].num+=k;t[x].sum+=k*id[pos];
if(l>=r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos,k);
else Modify(rson,mid+1,r,pos,k);
}
il ll Query(re int x,re int l,re int r,re int k)
{
if(l>=r) return t[x].num?(1ll*t[x].sum/t[x].num*k):0;
re int mid=l+r>>1;
if(k<=t[lson].num) return Query(lson,l,mid,k);
else return t[lson].sum+Query(rson,mid+1,r,k-t[lson].num);
}
int main()
{
m=gi();n=gi();
fp(i,1,m)
{
re int s=gi(),e=gi(),f=gi();
a[i*2-1]=(tas){s,f,1};a[i*2]=(tas){e+1,f,-1};id[i]=f;
}
sort(a+1,a+2*m+1);sort(id+1,id+1+m);
len=unique(id+1,id+1+m)-id-1;
for(re int i=1,j=1;i<=n;i++)
{
rt[i]=rt[i-1];
for(;a[j].t==i;j++)
{
re int x=lower_bound(id+1,id+1+len,a[j].w)-id;
Modify(rt[i],1,len,x,a[j].f);
}
}
fp(i,1,n)
{
re int X=gi(),A=gi(),B=gi(),C=gi(),Y;
Y=(1ll*A*ans+B)%C+1;ans=Query(rt[X],1,len,Y);
printf("%lld\n",ans);
}
return 0;
}
T4
Luogu2839 [国家集训队]middle
求左端点在[a,b]之间,右端点在[c,d]之间的序列中,最大的中位数。
考虑二分答案
Check把小于它的设为−1,大于等于它的设为1
维护三个玩意儿
[a,b]求一个最大后缀子段和
[c,d]求一个最大前缀子段和
[b+1,c−1]求一个和
加起来如果大于等于0,那么满足要求,即这个数还可以变大,否则就只能缩小
每个数开一个线段树来做,空间开不下,用主席树即可
Update:
- get一个新姿势“线段树返回结构体???
- RE不止是因为空间开炸了???
// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define lson t[x].ls
#define rson t[x].rs
#define re register
#define il inline
#define ll long long
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100;
struct seg{int sum,ls,rs,lm,rm;}t[N*10];
struct ppl
{
int id,d;
bool operator < (const ppl &o) const {return d<o.d;}
}num[N];
struct Res{int sum,ls,rs,lm,rm;}r[N*10];
int n,Q,ans,q[10],a[N],len,A,B,C,D,tot,rt[N];
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void Update(re int x)
{
t[x].lm=max(t[lson].lm,t[lson].sum+t[rson].lm);
t[x].rm=max(t[rson].rm,t[lson].rm+t[rson].sum);
t[x].sum=t[lson].sum+t[rson].sum;
}
il void Build(re int &x,re int l,re int r)
{
x=++tot;
if(l==r) {t[x].sum=t[x].lm=t[x].rm=1;return;}
re int mid=l+r>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
Update(x);
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
t[++tot]=t[x];x=tot;
if(l==r) {t[x].sum=t[x].lm=t[x].rm=-1;return;}
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos);else if(pos>mid)Modify(rson,mid+1,r,pos);
Update(x);
}
il Res Merge(Res x,Res y)
{
Res z;
z.sum=x.sum+y.sum;
z.lm=max(x.lm,y.lm+x.sum);
z.rm=max(y.rm,y.sum+x.rm);
return z;
}
il Res Query(re int x,re int l,re int r,re int ql,re int qr)
{
if(ql<=l&&r<=qr)
{
re Res tmp=(Res){t[x].sum,0,0,t[x].lm,t[x].rm};
return tmp;
}
re int mid=l+r>>1;
if(qr<=mid) return Query(lson,l,mid,ql,qr);
else if(ql>mid) return Query(rson,mid+1,r,ql,qr);
else return Merge(Query(lson,l,mid,ql,qr),Query(rson,mid+1,r,ql,qr));
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+48);
}
int main()
{
n=gi();fp(i,1,n) a[i]=num[i].d=gi(),num[i].id=i;
sort(a+1,a+1+n);
len=unique(a+1,a+1+n)-a-1;
fp(i,1,n) num[i].d=lower_bound(a+1,a+1+len,num[i].d)-a;
sort(num+1,num+1+n);
Q=gi();
Build(rt[1],1,n);
fp(i,2,n)
{
rt[i]=rt[i-1];
Modify(rt[i],1,n,num[i-1].id);
}
while(Q--)
{
A=gi();B=gi();C=gi();D=gi();
q[0]=(A+ans)%n+1,q[1]=(B+ans)%n+1,q[2]=(C+ans)%n+1,q[3]=(D+ans)%n+1;
sort(q,q+4);A=q[0],B=q[1],C=q[2],D=q[3];
re int res=0,dat,l=1,r=n;
while(l<=r)
{
re int mid=l+r>>1;
if(B+1<=C-1) dat=Query(rt[mid],1,n,B+1,C-1).sum;
else dat=0;
dat+=Query(rt[mid],1,n,A,B).rm+Query(rt[mid],1,n,C,D).lm;
if(dat>=0) res=num[mid].d,l=mid+1; else r=mid-1;
}
ans=a[res];
wri(ans);puts("");
}
return 0;
}
T5
Luogu2633 Count on a tree
问u和v这两个节点间第K小的点权。
主席树还是为值域线段树,维护数的个数(开w和o两个数组就成了,x无必要)
每个点的线段树版本由它的父亲加入它的点权得到
那么每个点的线段树存的就是它到根的所有点的点权
还是树上二分数字,还是线段树作差:
用rt[i]表示i这个点的线段树
那么就是这样作差rt[u]+rt[v]−rt[lca]−rt[fa[lca]]
这样就只包含了u,v路径上所有的点了
// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e5+100;
int n,m,o[N],h[N],cnt,w[N],x[N],len,tot,tim,sz[N],son[N],f[N],d[N],ans,rt[N],top[N];
struct Edge{int to,next;}e[N<<1];
struct seg{int ls,rs,num;}t[N*30];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+'0');
}
il void Build(re int &x,re int l,re int r)
{
x=++tot;
if(l==r) return;
re int mid=l+r>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int fa,re int l,re int r,re int pos)
{
t[x=++tot]=t[fa];t[x].num++;
if(l==r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,t[fa].ls,l,mid,pos);else Modify(rson,t[fa].rs,mid+1,r,pos);
}
il void dfs1(re int u,re int fa,re int deep)
{
sz[u]=1;f[u]=fa;d[u]=deep;
Modify(rt[u],rt[fa],1,len,x[u]);
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==fa) continue;
dfs1(v,u,deep+1);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
il void dfs2(re int u,re int up)
{
top[u]=up;
if(son[u]) dfs2(son[u],up);
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==f[u]||v==son[u]) continue;
dfs2(v,v);
}
}
il int get_lca(re int u,re int v)
{
while(top[u]^top[v])
{
if(d[top[u]]<d[top[v]]) swap(u,v);
u=f[top[u]];
}
if(d[u]>d[v]) swap(u,v);
return u;
}
il int Query(re int A,re int B,re int C,re int D,re int l,re int r,re int k)
{
if(l==r) return l;
re int tmp=t[t[A].ls].num+t[t[B].ls].num-t[t[C].ls].num-t[t[D].ls].num,mid=l+r>>1;
if(k<=tmp) return Query(t[A].ls,t[B].ls,t[C].ls,t[D].ls,l,mid,k);else return Query(t[A].rs,t[B].rs,t[C].rs,t[D].rs,mid+1,r,k-tmp);//swap(k,tmp)???!!!!!
}
int main()
{
memset(h,-1,sizeof(h));
n=gi();m=gi();
fp(i,1,n) o[i]=w[i]=gi();sort(o+1,o+1+n);///where is sort???
len=unique(o+1,o+1+n)-o-1;
fp(i,1,n) x[i]=lower_bound(o+1,o+1+len,w[i])-o;///why the last letter is x???
fp(i,1,n-1)
{
re int u=gi(),v=gi();
add(u,v);add(v,u);
}
Build(rt[0],1,len);
dfs1(1,0,1);dfs2(1,1);
while(m--)
{
re int u=gi()^ans,v=gi(),lca=get_lca(u,v),k=gi();
ans=o[Query(rt[u],rt[v],rt[lca],rt[f[lca]],1,len,k)];
wri(ans);puts("");
}
return 0;
}
T6
Luogu3302 [SDOI2013]森林
1.询问x-y这条链上点权的第k小。保证x,y在同一个连通块里。
2.连接x,y两点。保证x,y在不同的连通块里。
操作一和count on a tree一样,现在考虑操作二怎么处理。
题目中多次提到联通块,这意味者什么?
意味着加边过程实际上是联通块合并的过程。
我们可以先dfs遍历所有点,给每个点标上其所属的联通块。
当连边时,我们把联通块合并(当然要用启发式合并啦),让大的联通块吞并小的联通块时,我们要改变每个点的所属,改变它的父亲,改边点的层数,改变联通块的大小(就是链剖一开始维护的东西)。
但总不能重新链剖吧?
于是只能用倍增求lca,这样dfs时改变一个点的父亲后就可以更新倍增数组。
Update:
- 空间一定要多开,保险1e7???
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100;
int n,m,T,o[N],w[N],h[N],cnt,len,f[N][20],d[N],sz[N],ff[N],tot,rt[N],ans;
bool vis[N];
char s[10];
struct Edge{int to,next;}e[N<<1];
struct seg{int ls,rs,num;}t[N*20];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+'0');
}
il void Modify(re int &x,re int fa,re int l,re int r,re int pos)
{
t[x=++tot]=t[fa];t[x].num++;
if(l==r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,t[fa].ls,l,mid,pos);else Modify(rson,t[fa].rs,mid+1,r,pos);
}
il void dfs(re int u,re int fa,re int st)
{
d[u]=d[fa]+1;vis[u]=1;f[u][0]=fa;sz[st]++;ff[u]=st;
fp(i,1,16) f[u][i]=f[f[u][i-1]][i-1];
Modify(rt[u],rt[f[u][0]],1,len,w[u]);
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==fa) continue;
dfs(v,u,st);
}
}
il void Build(re int &x,re int l,re int r)
{
x=++tot;
if(l==r) return;
re int mid=l+r>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
}
il int get_lca(re int u,re int v)
{
if(d[u]<d[v]) swap(u,v);
fq(i,16,0)
if(d[u]-(1<<i)>=d[v]) u=f[u][i];
if(u==v) return u;
fq(i,16,0)
if(f[u][i]^f[v][i]) u=f[u][i],v=f[v][i];
return f[u][0];
}
il int Query(re int A,re int B,re int C,re int D,re int l,re int r,re int k)
{
if(l==r) return l;
re int tmp=t[t[A].ls].num+t[t[B].ls].num-t[t[C].ls].num-t[t[D].ls].num,mid=l+r>>1;
if(k<=tmp) return Query(t[A].ls,t[B].ls,t[C].ls,t[D].ls,l,mid,k);else return Query(t[A].rs,t[B].rs,t[C].rs,t[D].rs,mid+1,r,k-tmp);
}
int main()
{
memset(h,-1,sizeof(h));
gi();n=gi();m=gi();T=gi();
fp(i,1,n) o[i]=w[i]=gi(),ff[i]=i;///ff要初始值
sort(o+1,o+1+n);
len=unique(o+1,o+1+n)-o-1;
fp(i,1,n) w[i]=lower_bound(o+1,o+1+len,w[i])-o;
fp(i,1,m)
{
re int u=gi(),v=gi();
add(u,v);add(v,u);
}
Build(rt[0],1,len);
fp(i,1,n) if(!vis[i]) dfs(i,0,i);
while(T--)
{
scanf("%s",s);
if(s[0]=='Q')
{
re int u=gi()^ans,v=gi()^ans,lca=get_lca(u,v),k=gi()^ans;
ans=o[Query(rt[u],rt[v],rt[lca],rt[f[lca][0]],1,len,k)];
wri(ans),puts("");
}
else
{
re int u=gi()^ans,v=gi()^ans;
if(sz[ff[u]]<sz[ff[v]]) swap(u,v);
dfs(v,u,ff[u]);
add(u,v);add(v,u);
}
}
return 0;
}
T8
Luogu3293 [SCOI2016]美味
一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 \(a_i\)(1<=i<=n)。有 m 位顾客,第 i 位顾客的期望值为 \(b_i\),而他的偏好值为 \(x_i\) 。因此,第 i位顾客认为第j 道菜的美味度为\(b_i\bigotimes(a_j+x_i)\).他只能从第 \(l_i\) 道到第 \(r_i\) 道中选择。请你帮助他们找出最美味的菜。
二分\(a_j+x_i\)
针对\(b_i\)从大到小枚举位数,尽可能使大位为1.
这一位填1或0显然都可以缩小答案范围,于是在这个范围内查找看是否存在\(mid-x_i\)这一数即可。显然可以主席树维护。
Update:注意一下边界
// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100,len=1e5;
int n,m,w[N],tot,rt[N],b,l,x,r,ans,f,sz[N];
struct seg{int ls,rs,sz;}t[N*10];
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+'0');
}
il void Build(re int &x,re int l,re int r)
{
x=++tot;
if(l==r) return;
re int mid=l+r>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
t[++tot]=t[x];++t[x=tot].sz;
if(l==r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r,re int ql,re int qr)
{
if(ql<=l&&r<=qr) return t[B].sz-t[A].sz;
re int mid=l+r>>1,res=0;
if(ql<=mid) res=Query(t[A].ls,t[B].ls,l,mid,ql,qr);
if(qr>mid) res+=Query(t[A].rs,t[B].rs,mid+1,r,ql,qr);
return res;
}
int main()
{
n=gi();m=gi();
Build(rt[0],1,len);
fp(i,1,n) w[i]=gi(),rt[i]=rt[i-1],Modify(rt[i],0,len,w[i]);//数据范围这么小,连离散化都不需要。。。
while(m--)
{
ans=0;
b=gi(),x=gi(),l=gi(),r=gi();re int L,R;
fq(j,17,0)//别忘了处理最后一位
{
if((1<<j)&b) L=ans,R=ans+(1<<j)-1,f=0;//该位填0
else L=ans+(1<<j),R=ans+(1<<j+1)-1,f=1;//该位填1
if(!Query(rt[r],rt[l-1],0,len,max(0,L-x),min(R-x,len))) f^=1;//没这数
ans|=(f<<j);
}
wri(ans^b);puts("");
}
return 0;
}
T9
Luogu2468 [SDOI2010]粟粟的书架
[题面戳我][https://www.luogu.org/problemnew/show/P2468]
吐槽一句,强行二合一有意思不???
- 二维的50分,开数组\(sum[i][j][k]\),\(gs[i][j][k]\)分别记录左上点为\((1,1)\),右下点为\((i,j)\)的矩阵中大于等于\(k\)的数的和 和 个数。二分k即可。
- 一维的50分,开主席树维护页数,每次在主席树里找出足够大的和。(???)
Update:二维那里,二分完后可能有多余的个数。why?
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1050,len=1000;
int r,c,m,a,gs[210][210][1010],sum[210][210][1010],sm[500005],tot,rt[500005],L,R,h,ans;
struct seg{int ls,rs,sz,sum;}t[10000000];
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+'0');
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
t[++tot]=t[x];++t[x=tot].sz;t[x].sum+=pos;
if(l==r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r)
{
if(l==r) return (h+l-1)/l;
re int mid=l+r>>1,tmp=t[t[A].rs].sum-t[t[B].rs].sum;
if(tmp>=h) return Query(t[A].rs,t[B].rs,mid+1,r);
else {h-=tmp;return t[t[A].rs].sz-t[t[B].rs].sz+Query(t[A].ls,t[B].ls,l,mid);}
}
int main()
{
r=gi();c=gi();m=gi();
if(r>1)
{
fp(i,1,r)
fp(j,1,c)
{
re int x=gi();
fp(k,1,1000)
{
sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
gs[i][j][k]=gs[i-1][j][k]+gs[i][j-1][k]-gs[i-1][j-1][k];
if(x>=k) sum[i][j][k]+=x,gs[i][j][k]++;
}
}
while(m--)
{
re int l=1,r=1000,ans=1001,x=gi(),y=gi(),xx=gi(),yy=gi(),h=gi();
while(l<=r)
{
re int mid=l+r>>1;
if(sum[xx][yy][mid]-sum[x-1][yy][mid]-sum[xx][y-1][mid]+sum[x-1][y-1][mid]>=h) ans=mid,l=mid+1;
else r=mid-1;
}
if(ans==1001) {puts("Poor QLW");continue;}
re int ans1=gs[xx][yy][ans]-gs[x-1][yy][ans]-gs[xx][y-1][ans]+gs[x-1][y-1][ans],ans2=sum[xx][yy][ans]-sum[x-1][yy][ans]-sum[xx][y-1][ans]+sum[x-1][y-1][ans]-h;
wri(ans1-ans2/ans);puts("");
}
}
else
{
fp(i,1,c) a=gi(),rt[i]=rt[i-1],Modify(rt[i],1,1000,a);
while(m--)
{
gi();L=gi();gi();R=gi();h=gi();
if(t[rt[R]].sum-t[rt[L-1]].sum<h) {puts("Poor QLW");continue;}
else printf("%d\n",Query(rt[R],rt[L-1],1,1000));
}
}
return 0;
}
T10
Luogu2048 [NOI2010]超级钢琴
求给定序列中长度为L~R之间的前k大子序列的和
有一个很暴力的想法,就是把所有合法的状态丢到一个堆里面,然后依次取出最大值。这样的话时间是\(O(n^2logn)\),空间是\(O(n^2)\).
我们考虑优化这个过程。对于右端点相同的所有合法区间,我们只在堆中保留最大的一个,在取出这一个以后再丢入次大的,取出次大的再丢入次次大的,以此类推。这样可以保证正确性,同时空间被优化到了\(O(n)\)
现在我们考虑怎么查这个次次次次(这里有若干个次)大值。
首先把区间和转化成前缀和相减,因为我们固定了右端点,那么最大值就是对应了前缀和中减去的那个值最小。而第k大值刚好对应减去的那个数第k小,同时还要保证在一定区间范围内(L、R的限制)。所以离散前缀和以后主席树即可。
注意前缀和减去的部分可以是0,所以0也要被离散进去。
// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e6+100;
struct seg{int ls,rs,num;}t[N*10];
struct node
{
int x,k;ll w;
bool operator < (const node &o) const {return w<o.w;}
};
priority_queue<node>Q;
int n,k,L,R,w[N],o[N],len,tot,rt[N];
ll ans;
il int gi()
{
re int x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
t[++tot]=t[x];t[x=tot].num++;
if(l==r) return;
re int mid=l+r>>1;
if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r,re int k)
{
if(l==r) return l;
re int mid=l+r>>1,tmp=t[t[A].ls].num-t[t[B].ls].num;
if(k<=tmp) return Query(t[A].ls,t[B].ls,l,mid,k);
else return Query(t[A].rs,t[B].rs,mid+1,r,k-tmp);
}
int main()
{
n=gi();k=gi();L=gi();R=gi();++n;
fp(i,2,n) o[i]=w[i]=gi()+w[i-1];
sort(o+1,o+1+n);
len=unique(o+1,o+1+n)-o-1;
fp(i,1,n) w[i]=lower_bound(o+1,o+1+len,w[i])-o;
fp(i,1,n) rt[i]=rt[i-1],Modify(rt[i],1,len,w[i]);
fp(i,L+1,n) Q.push((node){i,1,o[w[i]]-o[Query(rt[i-L],rt[max(0,i-R-1)],1,len,1)]});
while(k--)
{
node tmp=Q.top();Q.pop();
ans+=tmp.w;
if(tmp.k==min(R-L+1,tmp.x-L)) continue;
tmp.k++;
tmp.w=o[w[tmp.x]]-o[Query(rt[tmp.x-L],rt[max(0,tmp.x-R-1)],1,len,tmp.k)];
Q.push(tmp);
}
printf("%lld\n",ans);
return 0;
}