区间第K小查询
description
给定一个长度为(n) 的序列,每次对于一个区间([l,r]) ,求出这段区间中第(k) 小的数的值。
(nle 10^5)
solution
首先考虑全局怎么做,即询问区间为([1,n]) 时。
我们可以建立权值线段树,对于其上的区间([l,r]) 记下全局有多少个数在([l,r]) 之间,查询时只用在其上二分即可。
倘若询问区间,我们也可以建出这段区间的权值线段树,然后查询,但显然复杂度爆炸。
受到前缀和优化区间和的启发,我们可以想到建立前缀的权值线段树,这样([l,r]) 的区间线段树就是(r) 的线段树减去(l-1) 的。如果每次暴力建还是无法承受,但注意到(i) 处的线段树是由(i-1) 处的线段树修改(mathcal O(log n)) 个节点得来的,于是直接上可持久化即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,s,cnt;
int a[N],b[N],root[N];
int sum[N<<5],L[N<<5],R[N<<5];
int build(int l,int r)
{
int rr=++cnt;
sum[rr]=0;
if(l==r) return rr;
int mid=(l+r)/2;
L[rr]=build(l,mid);
R[rr]=build(mid+1,r);
return rr;
}
int UP(int kk,int l,int r,int x)
{
int rr=++cnt;
sum[rr]=sum[kk]+1;
L[rr]=L[kk];
R[rr]=R[kk];
if(l==r) return rr;
int mid=(l+r)/2;
if(x<=mid)
L[rr]=UP(L[kk],l,mid,x);
else
R[rr]=UP(R[kk],mid+1,r,x);
return rr;
}
int Query(int u,int v,int l,int r,int k)
{
if(l==r) return l;
int xx=sum[L[v]]-sum[L[u]];
int mid=(l+r)/2;
if(xx>=k)
return Query(L[u],L[v],l,mid,k);
else
return Query(R[u],R[v],mid+1,r,k-xx);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+n+1);
s=unique(b+1,b+n+1)-(b+1);
root[0]=build(1,s);
for(int i=1;i<=n;i++)
{
int t=lower_bound(b+1,b+s+1,a[i])-b;
root[i]=UP(root[i-1],1,s,t);
}
while(m--)
{
int o,p,q;
cin>>o>>p>>q;
cout<<b[Query(root[o-1],root[p],1,s,q)]<<endl;
}
return 0;
}
树上计数
description
给出一个(n) 个结点的树,每个结点有一个整数权值。执行(m) 次询问,每次询问给出(u,v,k) ,求其简单路径上的第(k)小点权值。(n,mle 10^5)
solution
和上一道题类似,维护前缀权值线段树然后差分得到这条路径对应的线段树,是树上差分的技巧。不同点是(u) 的前缀线段树是由(u) 的父亲继承而来的。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,H=17;
int n,m,mx,rt[N],val[N],fa[N][20],tin[N],tout[N],tim,dep[N],cv[N];
vector<int>e[N];
struct SGT
{
int tot,ls[N<<5],rs[N<<5],sz[N<<5];
void upd(int&rt,int prt,int v,int l,int r)
{
rt=++tot;
ls[rt]=ls[prt],rs[rt]=rs[prt];
sz[rt]=sz[prt]+1;
if(l==r)return;int mid=l+r>>1;
if(v<=mid)upd(ls[rt],ls[prt],v,l,mid);
else upd(rs[rt],rs[prt],v,mid+1,r);
}
int query(int x,int y,int ac,int f,int k,int l,int r)
{
if(l==r)return l;int mid=l+r>>1;
int num=sz[ls[x]]+sz[ls[y]]-sz[ls[ac]]-sz[ls[f]];
if(k<=num)return query(ls[x],ls[y],ls[ac],ls[f],k,l,mid);
return query(rs[x],rs[y],rs[ac],rs[f],k-num,mid+1,r);
}
}T;
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
inline void lsh()
{
copy(val+1,val+n+1,cv+1);
sort(cv+1,cv+n+1);
mx=unique(cv+1,cv+n+1)-cv-1;
for(int i=1;i<=n;++i)
val[i]=lower_bound(cv+1,cv+mx+1,val[i])-cv;
}
inline void add(int x,int y){e[x].push_back(y);}
void dfs(int u,int pr)
{
fa[u][0]=pr;dep[u]=dep[pr]+1;tin[u]=++tim;
for(int i=1;(1<<i)<=dep[u];++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
T.upd(rt[u],rt[pr],val[u],1,mx);
for(int i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(v!=pr)dfs(v,u);
}tout[u]=++tim;
}
inline bool isac(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=H;~i;--i)
if(!isac(fa[x][i],y))x=fa[x][i];
return fa[x][0];
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;++i)val[i]=read();lsh();
for(int i=1,u,v;i<n;++i)
{
u=read(),v=read();
add(u,v),add(v,u);
}dfs(1,0);tin[0]=0,tout[0]=++tim;
while(m--)
{
int u=read(),v=read(),k=read();
int p=lca(u,v);
printf("%d
",cv[T.query(rt[u],rt[v],rt[p],rt[fa[p][0]],k,1,mx)]);
}
return 0;
}
树上异或
description
给定一棵(n) 个节点的树,点有点权,多次询问树上简单路径上所有点权值与给定值(d) 的异或最大值。
solution
仍然考虑如果放到全局怎么做。这是一个经典问题,即对于每个权值按二进制位从高到低地插入到(01Trie) 中,询问时也是从高位到低位考虑,当前位能异或得到(1) 那么就贪心地选择,因为即使后面的位全部是(1) 也不能大过当前位的(1) 。
搬到树上。和上一道题也是类似的树上差分,只不过将主席树替换为了可持久化(01Trie) 。可持久化的方法是一致的。(感觉(01Trie) 和线段树在某种意义上是等价的)
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,H=16;
namespace Trie
{
int ch[N<<5][2],sz[N<<5],tot;
inline void pre(){tot=0;}
inline int nd()
{
int p=++tot;ch[p][0]=ch[p][1]=0;
sz[p]=0;return p;
}
void ins(int&rt,int prt,int x,int dep=H)
{
rt=nd();
ch[rt][0]=ch[prt][0],ch[rt][1]=ch[prt][1];
sz[rt]=sz[prt]+1;
if(!(~dep))return;
int c=(x>>dep)&1;
ins(ch[rt][c],ch[prt][c],x,dep-1);
}
int query(int a,int b,int c,int d,int x,int dep=H)
{
if(!(~dep))return 0;
int t=(x>>dep)&1;t^=1;
int s=sz[ch[a][t]]+sz[ch[b][t]]-sz[ch[c][t]]-sz[ch[d][t]];
if(s)return (1<<dep)+query(ch[a][t],ch[b][t],ch[c][t],ch[d][t],x,dep-1);
t^=1;return query(ch[a][t],ch[b][t],ch[c][t],ch[d][t],x,dep-1);
}
}
int n,m,rt[N],val[N],in[N],out[N],tim,dep[N],fa[N][20];
vector<int>e[N];
void dfs(int u,int f)
{
in[u]=++tim;dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=H;++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
rt[u]=rt[f];Trie::ins(rt[u],rt[u],val[u]);
for(int v:e[u])if(v^f)dfs(v,u);
out[u]=++tim;
}
inline bool isac(int x,int y){return in[x]<=in[y]&&out[y]<=out[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=H;~i;--i)
if(!isac(fa[x][i],y))x=fa[x][i];
return fa[x][0];
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
for(int i=1;i<=n;++i)scanf("%d",val+i);
for(int i=1,u,v;i<n;++i)
scanf("%d%d",&u,&v),
e[u].push_back(v),e[v].push_back(u);
Trie::pre();tim=0;dfs(1,0);out[0]=++tim;
while(m--)
{
int x,y,d;scanf("%d%d%d",&x,&y,&d);
int p=lca(x,y);
printf("%d
",Trie::query(rt[x],rt[y],rt[p],rt[fa[p][0]],d));
}
for(int i=1;i<=n;++i)e[i].clear();
}
return 0;
}
自带版本控制功能的IDE
description
维护一种数据结构,支持三种操作。
1.在p位置插入一个字符串s
2.从p位置开始删除长度为c的字符串
3.输出第v个历史版本中从p位置开始的长度为c的字符串
强制在线。字符串总长度不超过(10^6)
solution
可持久化平衡树模板。
可持久化平衡树一般采用可持久化非旋(Treap) 。考虑只有(split) 和(merge) 两种操作会改变树的形态,于是只在这两个操作时需要复制节点以保留原先的版本。其他操作和普通(Treap) 相同。
不过由于(merge) 时的节点其实就是(split) 时新创的节点,因此(merge) 时也不需要新建节点。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int ddd;
mt19937 rd(time(0));
namespace Treap
{
int tot=0;struct node{int ls,rs,v,sz,fix;}t[N<<5];
inline int nd(int r)
{
int p=++tot;t[p]={0,0,r,1,rd()};
return p;
}
inline void upd(int x){t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz+1;}
void split(int p,int d,int&l,int&r)
{
if(!p){l=r=0;return;}
int now=++tot;t[now]=t[p];
if(t[t[p].ls].sz+1<=d)
{
l=now,split(t[p].rs,d-1-t[t[p].ls].sz,t[l].rs,r);
upd(l);
}
else
{
r=now,split(t[p].ls,d,l,t[r].ls);
upd(r);
}
}
int merge(int l,int r)
{
if(!l||!r)return l^r;
if(t[l].fix>t[r].fix)
{
t[l].rs=merge(t[l].rs,r);
upd(l);return l;
}
else
{
t[r].ls=merge(l,t[r].ls);
upd(r);return r;
}
}
inline void ins(int&rt,int p,char*s)
{
int len=strlen(s);
int a,b;split(rt,p,a,b);
for(int i=0;i<len;++i)
a=merge(a,nd(s[i]));
rt=merge(a,b);
}
inline void del(int&rt,int p,int t)
{
int a,b,c;
split(rt,p-1,a,b);
split(b,t,b,c);
rt=merge(a,c);
}
void go(int u)
{
if(!u)return;
go(t[u].ls);
putchar(t[u].v);if(t[u].v=='c')++ddd;
go(t[u].rs);
}
inline void print(int&rt,int p,int t)
{
int a,b,c;
split(rt,p-1,a,b);
split(b,t,b,c);
go(b);puts("");
rt=merge(merge(a,b),c);
}
}
using namespace Treap;
int rt[N];char s[N];
int main()
{
int q;scanf("%d",&q);int now=0;
while(q--)
{
int opt,p,c,v;scanf("%d",&opt);
if(opt==1)
{
++now;rt[now]=rt[now-1];
scanf("%d%s",&p,s);p-=ddd;
ins(rt[now],p,s);
}
else if(opt==2)
{
++now;rt[now]=rt[now-1];
scanf("%d%d",&p,&c);p-=ddd,c-=ddd;
del(rt[now],p,c);
}
else
{
scanf("%d%d%d",&v,&p,&c);
v-=ddd,p-=ddd,c-=ddd;
print(rt[v],p,c);
}
}
return 0;
}