可持久化数据结构
P3919 【模板】可持久化线段树 1(可持久化数组)
链接:https://www.luogu.com.cn/problem/P3919
题意:你需要维护这样的一个长度为 n n n 的数组,支持如下几种操作
- 在某个历史版本上修改某一个位置上的值
- 访问某个历史版本上的某一位置的值,并输出这个值
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
int n,m,a[maxn];
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R)
{
st[rt]=a[L];
return rt;
}
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R,int val)
{
int rt=++no;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R)
{
st[rt]=val;
return rt;
}
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,val);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,val);
return rt;
}
int query(int rt,int p,int L,int R)
{
if(L==R) return st[rt];
int mid=(L+R)>>1;
if(p<=mid) return query(ls[rt],p,L,mid);
if(p>mid) return query(rs[rt],p,mid+1,R);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
root[0]=build(1,n);
for(int i=1; i<=m; ++i)
{
int id,ty,pos,value;
scanf("%d%d%d",&id,&ty,&pos);
if(ty==1)
{
scanf("%d",&value);
root[i]=update(root[id],pos,1,n,value);
}
else
{
root[i]=root[id];
printf("%d\n",query(root[id],pos,1,n));
}
}
return 0;
}
P1383 高级打字机 (可持久化数组)
链接:https://www.luogu.com.cn/problem/P1383
题意:早苗入手了最新的高级打字机。请为这种高级打字机设计一个程序,支持如下 3 种操作:
- T x:在文章末尾打下一个小写字母 x x x (Type 操作 )
- U x:撤销最后的 x x x 次修改操作 (Undo 操作)
- Q x:询问当前文章中第 x x x 个字母并输出 (Query 操作不算修改操作)
文章一开始可以视为空串。每次输出 Query 操作的答案
对于
100
%
100\%
100% 的数据
n
≤
100000
n \le 100000
n≤100000;保证 Undo 操作不会撤销 Undo 操作。
对于
200
%
200\%
200% 的数据
n
≤
100000
n \le 100000
n≤100000;Undo 操作可以撤销 Undo 操作。
思路:
- 一开始看到题目的时候,感觉直接拿个vector 模拟一下就可以了。后来才发现撤销操作可以撤销撤销操作,才发现是自己太天真了。
- 那么就只能用主席树模拟了,使用可持久化数组即可。
- 记录好 Type操作 和 Undo 操作的版本编号(vid)。同时,需要记录当前版本是在哪个版本的基础上修改的。所以开一个 id 数字标记 vid 的依赖版本,类似并查集找队长的感觉。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,len[maxn],id[maxn];
int root[maxn],ls[maxn*30],rs[maxn*30],no;
char st[maxn*30];
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R,char val)
{
int rt=++no;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R)
{
st[rt]=val;
return rt;
}
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,val);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,val);
return rt;
}
char query(int rt,int p,int L,int R)
{
if(L==R) return st[rt];
int mid=(L+R)>>1;
if(p<=mid) return query(ls[rt],p,L,mid);
if(p>mid) return query(rs[rt],p,mid+1,R);
}
int main()
{
scanf("%d",&n);
root[0]=build(1,n);
int vid=0;
for(int i=1; i<=n; ++i)
{
int x;
char op[2],val[2];
scanf("%s",op);
if(op[0]=='T')
{
scanf("%s",val);
vid++;
int pre=id[vid-1];
len[vid]=len[pre]+1;
root[vid]=update(root[pre],len[vid],1,n,val[0]);
id[vid]=vid;
}
else if(op[0]=='Q')
{
scanf("%d",&x);
printf("%c\n",query(root[vid],x,1,n));
}
else if(op[0]=='U')
{
scanf("%d",&x);
vid++;
int pre=id[vid]=vid-1-x;
len[vid]=len[pre];
root[vid]=root[pre];
}
}
return 0;
}
P3834 【模板】可持久化线段树 2(主席树第 k 小)
链接:https://www.luogu.com.cn/problem/P3834
题意:求解区间第 k 小
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
int n,m,a[maxn];
vector<int> allx;
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int k,int L,int R)
{
if(L==R) return L;
int mid=(L+R)>>1;
int cnt=st[ls[now]]-st[ls[pre]];
if(k<=cnt) return query(ls[pre],ls[now],k,L,mid);
else return query(rs[pre],rs[now],k-cnt,mid+1,R);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a[i]);
allx.push_back(a[i]);
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
int tot=allx.size();
root[0]=build(1,n);
for(int i=1; i<=n; ++i)
{
int p=lower_bound(allx.begin(),allx.end(),a[i])-allx.begin()+1;
root[i]=update(root[i-1],p,1,tot);
}
int l,r,k;
while(m--)
{
scanf("%d%d%d",&l,&r,&k);
int p=query(root[l-1],root[r],k,1,tot);
printf("%d\n",allx[p-1]);
}
return 0;
}
P2633 Count on a tree (树上主席树第 k 小)
链接:https://www.luogu.com.cn/problem/P2633
题意:查询 u、v 路径上点权第 k 小
思路:dfs时建树,然后取前缀和作差。只需要 u 的前缀和 v 的前缀和,lca(u,v)的前缀和,以及 fa[lca(u,v)] 的前缀和,对这些做差即可可得想要的权值线段树。然后就是普通的查询第 k 小了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m,tot;
int a[maxn];
vector<int> allx,e[maxn];
int fa[maxn][21],depth[maxn];
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int u,int v,int x,int y,int k,int L,int R)
{
if(L==R) return L;
int mid=(L+R)>>1;
int cnt=st[ls[u]]+st[ls[v]]-st[ls[x]]-st[ls[y]];
if(k<=cnt) return query(ls[u],ls[v],ls[x],ls[y],k,L,mid);
else return query(rs[u],rs[v],rs[x],rs[y],k-cnt,mid+1,R);
}
void dfs(int u,int f)
{
fa[u][0]=f;
for(int i=1; i<=20; ++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
depth[u]=depth[f]+1;
int p=lower_bound(allx.begin(),allx.end(),a[u])-allx.begin()+1;
root[u]=update(root[f],p,1,tot);
for(auto v: e[u])
{
if(v==f) continue;
dfs(v,u);
}
}
int lca(int u,int v)
{
if(depth[u]<depth[v]) swap(u,v);
int dis=depth[u]-depth[v];
for(int i=0; (1<<i)<=dis; ++i)
if(dis>>i&1) u=fa[u][i];
if(u==v) return u;
for(int i=20; i>=0; --i)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]),allx.push_back(a[i]);
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
for(int i=1; i<=n-1; ++i)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
tot=allx.size();
root[0]=build(1,tot);
dfs(1,0);
int ans=0;
while(m--)
{
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
u=u^ans;
int x=lca(u,v);
int y=fa[x][0];
ans=query(root[u],root[v],root[x],root[y],k,1,tot);
ans=allx[ans-1];
printf("%d\n",ans);
}
return 0;
}
P3567 [POI2014]KUR-Couriers (主席树)
链接:https://www.luogu.com.cn/problem/P3567
题意:查询给定区间内是否有一个数的数量严格大于区间的一半
思路:严格大于表明了答案唯一。这样的话,在主席树上一个 log 就能解决了 。主要区别就在于 query 函数的写法,其他都一样。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5;
int n,m,a[maxn];
vector<int> allx;
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no;
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int len,int L,int R)
{
if(L==R) return st[now]-st[pre]>=len?L:0;
int mid=(L+R)>>1;
int cnt1=st[ls[now]]-st[ls[pre]];
int cnt2=st[rs[now]]-st[rs[pre]];
if(cnt1>cnt2) return query(ls[pre],ls[now],len,L,mid);
else if(cnt1<cnt2) return query(rs[pre],rs[now],len,mid+1,R);
else return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a[i]);
allx.push_back(a[i]);
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
int tot=allx.size();
for(int i=1; i<=n; ++i)
{
int p=lower_bound(allx.begin(),allx.end(),a[i])-allx.begin()+1;
root[i]=update(root[i-1],p,1,tot);
}
int l,r,k;
while(m--)
{
scanf("%d%d",&l,&r);
int p=query(root[l-1],root[r],(r-l+1)/2+1,1,tot);
printf("%d\n",p?allx[p-1]:0);
}
return 0;
}
P2468 [SDOI2010]粟粟的书架
链接:https://www.luogu.com.cn/problem/P2468
题意:给定一个 R × C R\times C R×C 的矩阵,每次询问为 ( r 1 , c 1 , r 2 , c 2 , h ) (r_1,c_1,r_2,c_2,h) (r1,c1,r2,c2,h),问一个子矩阵至少需要取多少个数才能大于 h 。
对于50%的数据,满足
R
,
C
≤
200
,
M
≤
200
,
000
R, C≤200,M≤200,000
R,C≤200,M≤200,000
另有50%的数据,满足
R
=
1
,
C
≤
500
,
000
,
M
≤
20
,
000
R=1,C≤500,000,M≤20,000
R=1,C≤500,000,M≤20,000
对于100%的数据,满足
1
≤
P
i
,
j
≤
1
,
000
,
1
≤
H
i
≤
2
,
000
,
000
,
000
1≤P_{i,j}≤1,000,1≤H_i≤2,000,000,000
1≤Pi,j≤1,000,1≤Hi≤2,000,000,000
思路:通过数据范围可以知道,对于前50%的数据,可以用二维前缀和 + 二分解决。对于后50%的数据,可以用主席树 + 二分解决
- 每次二分完之后得到的并不是恰好大于 h 的数据,还需要用当前二分得到的答案 l 进行调整,首先可以确定的是 l 这个位置一定是有数字的,不然不可能会得到这个 l
- 加上 l 的位置的数会大于等于 h ,减去 l 位置的数就会小于 h 。因此对 l 这个位置进行计算,将多余的减去即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5;
int n,m,t;
int cnt[210][210][1010],sum[210][210][1010];
int p[210][210];
int getsum(int r1,int c1,int r2,int c2,int k)
{
return sum[r2][c2][k]-sum[r2][c1-1][k]-sum[r1-1][c2][k]+sum[r1-1][c1-1][k];
}
void solve1()
{
for(int i=1; i<=n; ++i)
for(int j=1; j<=m; ++j)
scanf("%d",&p[i][j]);
for(int k=1; k<=1000; ++k)
for(int i=1; i<=n; ++i)
for(int j=1; j<=m; ++j)
{
cnt[i][j][k]=cnt[i-1][j][k]+cnt[i][j-1][k]-cnt[i-1][j-1][k];
sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
if(p[i][j]>=k) cnt[i][j][k]++,sum[i][j][k]+=p[i][j];
}
int r1,c1,r2,c2,h;
while(t--)
{
scanf("%d%d%d%d%d",&r1,&c1,&r2,&c2,&h);
if(getsum(r1,c1,r2,c2,1)<h)
{
puts("Poor QLW");
continue;
}
int l=1,r=1000;
while(l<r)
{
int mid=(l+r+1)>>1;
if(getsum(r1,c1,r2,c2,mid)>=h) l=mid;
else r=mid-1;
}
int ans=cnt[r2][c2][l]-cnt[r2][c1-1][l]-cnt[r1-1][c2][l]+cnt[r1-1][c1-1][l]-(getsum(r1,c1,r2,c2,l)-h)/l;
printf("%d\n",ans);
}
}
int a[maxn];
int root[maxn],ls[maxn*20],rs[maxn*20],no;
struct Node
{
ll sum,cnt;
} st[maxn*20];
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt].sum=st[pre].sum+p;
st[rt].cnt=st[pre].cnt+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int querysum(int pre,int now,int l,int r,int L,int R)
{
if(l<=L&&R<=r) return st[now].sum-st[pre].sum;
int mid=(L+R)>>1;
int ans=0;
if(l<=mid) ans+=querysum(ls[pre],ls[now],l,r,L,mid);
if(r>mid) ans+=querysum(rs[pre],rs[now],l,r,mid+1,R);
return ans;
}
int querycnt(int pre,int now,int l,int r,int L,int R)
{
if(l<=L&&R<=r) return st[now].cnt-st[pre].cnt;
int mid=(L+R)>>1;
int ans=0;
if(l<=mid) ans+=querycnt(ls[pre],ls[now],l,r,L,mid);
if(r>mid) ans+=querycnt(rs[pre],rs[now],l,r,mid+1,R);
return ans;
}
void solve2()
{
for(int i=1; i<=m; ++i) scanf("%d",&a[i]);
for(int i=1; i<=m; ++i)
root[i]=update(root[i-1],a[i],1,1000);
int r1,c1,r2,c2,h;
while(t--)
{
scanf("%d%d%d%d%d",&r1,&c1,&r2,&c2,&h);
if(st[root[c2]].sum-st[root[c1-1]].sum<h)
{
puts("Poor QLW");
continue;
}
int l=1,r=1000,R=1000;
while(l<r)
{
int mid=(l+r+1)>>1;
if(querysum(root[c1-1],root[c2],mid,R,1,1000)>=h) l=mid;
else r=mid-1;
}
int ans=querycnt(root[c1-1],root[c2],l,R,1,1000);
ans-=(querysum(root[c1-1],root[c2],l,R,1,1000)-h)/l;
printf("%d\n",ans);
}
}
int main()
{
scanf("%d%d%d",&n,&m,&t);
if(n>1) solve1();
else solve2();
return 0;
}
P3293 [SCOI2016]美味
链接:https://www.luogu.com.cn/problem/P3293
题意:有 n 个数,每个数为 a i a_i ai 。m个询问,每次询问为 ( b , x , l , r ) (b,x,l,r) (b,x,l,r) ,让你在区间 [ l , r ] [l,r] [l,r] 中选择一个 a i a_i ai 使得 b ⊕ ( a i + x ) b\oplus (a_i+x) b⊕(ai+x) 的值最大,输出这个最大值
对于 100% 的数据,满足 1 ≤ n ≤ 2 × 1 0 5 , 0 ≤ a i , b i , x i < 1 0 5 , 1 ≤ l i ≤ r i ≤ n ( 1 ≤ i ≤ m ) , 1 ≤ m ≤ 1 0 5 1 \le n \le 2 \times 10^5 ,0 \le a_i,b_i,x_i < 10^5,1 \le l_i \le r_i \le n (1 \le i \le m),1 \le m \le 10^5 1≤n≤2×105,0≤ai,bi,xi<105,1≤li≤ri≤n(1≤i≤m),1≤m≤105
思路:
- 按位贪心,从高位开始贪心,假设当前贪心到第 i 位,前面几位累积的答案为 ans 。
- 假设 b 的第 i 位为 1 ,则
a
+
x
a+x
a+x 第
i
i
i 位取
0
0
0 。因此 ,
a
n
s
≤
a
+
x
≤
a
n
s
+
2
i
−
1
ans\le a+x\le ans+2^i-1
ans≤a+x≤ans+2i−1
这样 a a a 需要满足 a n s − x ≤ a ≤ a n s + 2 i − 1 − x ans-x\le a\le ans+2^i-1-x ans−x≤a≤ans+2i−1−x,区间查询存不存在这样的 a 即可。 - 假设 b 的第 i 位为 0 ,则 a + x a+x a+x 第 i i i 位取 1 1 1。因此 , a n s + 2 i ≤ a + x ≤ a n s + 2 i + 1 − 1 ans+2^i\le a+x\le ans+2^{i+1}-1 ans+2i≤a+x≤ans+2i+1−1
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,tot=1e5;
int n,m,a;
int root[maxn],ls[maxn*40],rs[maxn*40],st[maxn*40],no=0;
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int l,int r,int L,int R)
{
if(l>r) return 0;
if(l<=L&&R<=r) return st[now]-st[pre];
int mid=(L+R)>>1;
int ans=0;
if(l<=mid) ans+=query(ls[pre],ls[now],l,r,L,mid);
if(r>mid) ans+=query(rs[pre],rs[now],l,r,mid+1,R);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a);
root[i]=update(root[i-1],a,0,tot);
}
int b,x,l,r;
while(m--)
{
scanf("%d%d%d%d",&b,&x,&l,&r);
int ans=0;
for(int i=17; i>=0; --i)
{
if(b>>i&1)
{
int l1=ans-x,r1=ans+(1<<i)-1-x;
l1=max(l1,0),r1=min(r1,tot);
int cnt=query(root[l-1],root[r],l1,r1,0,tot);
if(cnt==0) ans+=(1<<i);
}
else
{
int l1=ans+(1<<i)-x,r1=ans+(1<<i+1)-1-x;
l1=max(l1,0),r1=min(r1,tot);
int cnt=query(root[l-1],root[r],l1,r1,0,tot);
if(cnt>0) ans+=(1<<i);
}
}
printf("%d\n",ans^b);
}
return 0;
}