可持久化数据结构

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 n100000;保证 Undo 操作不会撤销 Undo 操作。
对于 200 % 200\% 200% 的数据 n ≤ 100000 n \le 100000 n100000;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,C200M200,000
另有50%的数据,满足 R = 1 , C ≤ 500 , 000 , M ≤ 20 , 000 R=1,C≤500,000,M≤20,000 R1C500,000M20,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 1Pi,j1,0001Hi2,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 1n2×105,0ai,bi,xi<1051lirin(1im)1m105

思路

  • 按位贪心,从高位开始贪心,假设当前贪心到第 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 ansa+xans+2i1
    这样 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 ansxaans+2i1x,区间查询存不存在这样的 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+2ia+xans+2i+11
#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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值