Loading

正睿——可持久化数据结构刷题笔记

主席树

T1

链接

我们考虑处理出一个数组,表示第二个数组的某个数在第一个数组出现的位置,不难发现,如果以下标作为横坐标,数组值作为纵坐标,这个问题就是一个二维数点问题。我们可以用主席树来做这个事情。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 30000100
#define M 1000100
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct Node{
    int sum,ls,rs;
}p[N];
int Root[M],tot;

#define ls(k) p[k].ls
#define rs(k) p[k].rs
struct SegmentTree{
    inline int NewNode(){return ++tot;}
    inline void PushUp(int k){p[k].sum=p[ls(k)].sum+p[rs(k)].sum;}
    inline int Insert(int &k,int l,int r,int w,int x){
        int now=NewNode();
        p[now]=p[k];
        if(l==r){p[now].sum++;return now;}
        int mid=(l+r)>>1;
        if(w<=mid) ls(now)=Insert(ls(k),l,mid,w,x);
        else rs(now)=Insert(rs(k),mid+1,r,w,x); 
        PushUp(now);return now;
    }  
    inline int AskSum(int k,int l,int r,int z,int y){
        if(l==z&&r==y) return p[k].sum;
        int mid=(l+r)>>1;
        if(y<=mid) return AskSum(ls(k),l,mid,z,y);
        else if(z>mid) return AskSum(rs(k),mid+1,r,z,y);
        else return AskSum(ls(k),l,mid,z,mid)+AskSum(rs(k),mid+1,r,mid+1,y);
    }
}st;

int a[M],b[M],id[M],n,m,ans;

inline int f(int x){return (x-1+ans)%n+1;}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++){read(a[i]);id[a[i]]=i;}
    for(int i=1;i<=n;i++){
        read(b[i]);Root[i]=st.Insert(Root[i-1],1,n,id[b[i]],1);
        // printf("id=%d\n",id[b[i]]);
    }
    // printf("here\n");
    read(m);
    // printf("m=%d\n",m);
    for(int i=1;i<=m;i++){
        // printf("i=%d\n",i);
        int A,B,C,D;read(A);read(B);read(C);read(D);
        A=f(A),B=f(B),C=f(C),D=f(D);
        if(A>B) swap(A,B);if(C>D) swap(C,D);
        // printf("A=%d B=%d C=%d D=%d\n",A,B,C,D);
        ans=st.AskSum(Root[D],1,n,A,B)-st.AskSum(Root[C-1],1,n,A,B);
        printf("%d\n",ans);
        ans++;
    }
    return 0;
}//

T2

链接

这个题直接做我们思路,我们考虑转化成判定问题,注意我们可以首先按照美味度排序,然后二分一个美味度,去考虑是否存在合法解,可以考虑把所有美味度比这个美味度大的果汁全部加进线段树,然后再线段树上二分,因为要对每一个前缀都建立一棵线段树,考虑用主席树优化。

然后需要注意有可能有价格相同的果汁,所以 Insert 的时候不要直接赋值,最好加起来。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 100010
#define M 2000010
using namespace std;

const int INF=(1e18)+1;
const int Len=1e5;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct Node{
    int SumP,SumL,ls,rs;
    inline Node(){}
    inline Node(int SumP,int SumL) : SumP(SumP),SumL(SumL) {}
};

int Root[N],rtt,tot;
Node p[M];

#define ls(k) p[k].ls
#define rs(k) p[k].rs
struct Segment_Tree{
    inline int NewNode(){return ++tot;}
    inline void PushUp(int k){
        p[k].SumL=p[ls(k)].SumL+p[rs(k)].SumL;
        p[k].SumP=p[ls(k)].SumP+p[rs(k)].SumP;
    }
    inline void Insert(int &k,int last,int l,int r,int w,int P,int L){
        k=NewNode();p[k]=p[last];
        if(l==r){
            p[k].SumL+=L;p[k].SumP+=P*L;return;
        }
        int mid=(l+r)>>1;
        if(w<=mid) Insert(ls(k),ls(last),l,mid,w,P,L);
        else Insert(rs(k),rs(last),mid+1,r,w,P,L);
        PushUp(k);
    }
    inline int Binary(int k,int l,int r,int L){
        if(p[k].SumL<L) return INF;
        if(l==r){
            if(p[k].SumL<L) return INF;
            return p[k].SumP/p[k].SumL*L;
        }
        int mid=(l+r)>>1;
        if(p[ls(k)].SumL>=L) return Binary(ls(k),l,mid,L);
        else return p[ls(k)].SumP+Binary(rs(k),mid+1,r,L-p[ls(k)].SumL);
    }
}tr;

struct Juice{
    int jd,jp,jl;
    inline Juice(){}
    inline Juice(int jd,int jp,int jl) : jd(jd),jp(jp),jl(jl) {}
    inline bool operator < (const Juice &b)const{return jd>b.jd;}
}a[N];

int n,m;

inline void Init(){
    read(n);read(m);
    for(int i=1;i<=n;i++){
        read(a[i].jd);read(a[i].jp);read(a[i].jl);
    }
    sort(a+1,a+n+1);
    // for(int i=1;i<=n;i++){
    //     printf("%lld %lld %lld\n",a[i].jd,a[i].jp,a[i].jl);
    // }puts("");
    for(int i=1;i<=n;i++){
        rtt++;
        tr.Insert(Root[rtt],Root[rtt-1],1,Len,a[i].jp,a[i].jp,a[i].jl);
    }
}

inline bool Check(int mid,int G,int L){
    int NowAns=tr.Binary(Root[mid],1,Len,L);
    return NowAns<=G;
}

inline int Binary(int G,int L){
    int l=1,r=n,ans=n+1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(Check(mid,G,L)){
            ans=mid;r=mid-1;
        }
        else l=mid+1;
    }
    // printf("ans=%lld\n",ans);
    return ans!=n+1?a[ans].jd:(-1);
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    Init();
    for(int i=1;i<=m;i++){
        int G,L;read(G);read(L);
        int ans=Binary(G,L);
        printf("%lld\n",ans);
    }
    return 0;
}

T3

链接

这个题是求中位数,我们考虑二分一个数 \(x\),然后把小于这个数的变成 \(-1\),大于等于这个数的变成 \(1\),这样等价于我们要求出一个合法区间,这个区间权值和大于等于 \(0\),如果能找到这样的一个区间的话,就代表我们可以往大了去二分。

考虑主席树,我们先把所有值对应的线段树处理出来,二分的时候相当于询问前缀最大,后缀最大,以及区间和,之前有一种想法比较容易想,但是需要主席树区间加,虽然也可以做,但是这样无法预判节点个数,相比之下,如果我们只有单点加的话,节点总量是一定的。这里要注意,空间一定不要忘了一开始的那 \(4*n\) 个节点的线段树。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 40010
#define M 400010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

struct Node{
    int Sum,PreSum,SufSum;
    inline Node(){}
    inline Node(int Sum,int PreSum,int SufSum) : Sum(Sum),PreSum(PreSum),SufSum(SufSum) {}
    inline Node operator + (const Node &b)const{
        Node c(0,0,0);
        c.Sum=Sum+b.Sum;
        c.PreSum=Max(PreSum,Sum+b.PreSum);
        c.SufSum=Max(b.SufSum,b.Sum+SufSum);return c;
    }
}p[M];

int Root[N],rtt,tot,Ls[M],Rs[M];

#define ls(k) Ls[k]
#define rs(k) Rs[k]
struct SegmentTree{
    inline int NewNode(){return ++tot;}
    inline void PushUp(int k){p[k]=p[ls(k)]+p[rs(k)];}
    inline void Build(int &k,int l,int r){
        k=NewNode();
        if(l==r){
            p[k]=Node(1,1,1);return;
        }
        int mid=(l+r)>>1;
        Build(ls(k),l,mid);Build(rs(k),mid+1,r);
        PushUp(k);
    }
    inline void Insert(int &k,int last,int l,int r,int w,int x){
        k=NewNode();p[k]=p[last];ls(k)=ls(last);rs(k)=rs(last);
        if(l==r){
            p[k]=Node(x,x,x);return;
        }
        int mid=(l+r)>>1;
        if(w<=mid) Insert(ls(k),ls(last),l,mid,w,x);
        else Insert(rs(k),rs(last),mid+1,r,w,x);
        PushUp(k);
    }
    inline Node AskMax(int k,int l,int r,int z,int y){
        if(l==z&&r==y) return p[k];
        int mid=(l+r)>>1;
        if(y<=mid) return AskMax(ls(k),l,mid,z,y);
        else if(z>mid) return AskMax(rs(k),mid+1,r,z,y);
        else return AskMax(ls(k),l,mid,z,mid)+AskMax(rs(k),mid+1,r,mid+1,y);
    }
}tr;

int n,Q,a[N],b[N],len,rk[N];
vector<int> v[N];

inline void Init(){
    read(n);
    for(int i=1;i<=n;i++){
        read(a[i]);b[i]=a[i];
    }
    sort(b+1,b+n+1);
    len=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++){
        int Rank=lower_bound(b+1,b+n+1,a[i])-b;
        rk[Rank]=a[i];a[i]=Rank;v[a[i]].push_back(i);
    }
    tr.Build(Root[1],1,n);rtt=1;
    for(int i=2;i<=len;i++){
        // printf("i=%d\n",i);
        rtt++;int last=Root[rtt-1];
        for(int now:v[i-1]){
            // printf("now=%d\n",now);
            tr.Insert(Root[rtt],last,1,n,now,-1);
            last=Root[rtt];
        }
        // printf("%d\n",p[Root[rtt]].Sum);
    }
    // printf("rtt=%d\n",rtt);
    // for(int i=0;i<=rtt;i++) printf("%d ",Root[i]);puts("");
    // for(int i=0;i<=rtt;i++){
    //     printf("p[%d].sum=%d\n",Root[i],p[Root[i]].Sum);
    // }
}

inline bool Check(int mid,int a,int b,int c,int d){
    // printf("mid=%d a=%d b=%d c=%d d=%d\n",mid,a,b,c,d);
    Node Max1=tr.AskMax(Root[mid],1,n,a,b);
    Node Max2=tr.AskMax(Root[mid],1,n,c,d);
    Node Max3(0,0,0);
    if(b+1<=c-1) Max3=tr.AskMax(Root[mid],1,n,b+1,c-1);
    // printf("Max1.sum=%d\n",Max1.SufSum);
    // printf("Max2.sum=%d\n",Max2.PreSum);
    // printf("Max3.sum=%d\n",Max3.Sum);
    return Max1.SufSum+Max3.Sum+Max2.PreSum>=0;
}

inline int Binary(int a,int b,int c,int d){
    int l=1,r=len,ans=1;
    // printf("%d %d %d %d\n",a,b,c,d);exit(0);
    while(l<=r){
        int mid=(l+r)>>1;
        if(Check(mid,a,b,c,d)){
            ans=mid;l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}

int Ans,P[6];

inline void Solve(){
    read(Q);
    for(int i=1;i<=Q;i++){
        read(P[0]);read(P[1]);
        read(P[2]);read(P[3]);
        for(int j=0;j<=3;j++) P[j]=(P[j]+Ans)%n;
        for(int j=0;j<=3;j++) P[j]++;
        sort(P,P+4);
        int ans=Binary(P[0],P[1],P[2],P[3]);
        // printf("ans=%d\n",ans);
        Ans=rk[ans];
        printf("%d\n",Ans);
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    Init();
    Solve();
}

可持久化并查集

这个东西就是并查集加上可持久化数组,考场上还是不建议写这种东西,大多数可持久化并查集的题目都可以用 Kruskal 重构树替代,下面会简单的介绍 Kruskal 重构树的一些简单应用。

关于可持久化并查集的代码,之前已经讲过,这里不再赘述。

T1

链接

注意到我们可以从高到低按照海拔枚举道路,然后用可持久化并查集维护每个版本的连通块以及连通块内距离 \(1\) 最短的路程是多少,我们可以实现预处理与 \(1\) 节点的最短路。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 300010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Min(T a,T b){return a<b?a:b;}

struct edge{
    int from,to,next,w,High;
    inline void Init(int fr_,int to_,int ne_,int w_,int h_){from=fr_;to=to_;next=ne_;w=w_;High=h_;}
    inline bool operator < (const edge &b)const{return High>b.High;}
}li[N<<2];
int head[N],tail;

inline void Add(int from,int to,int w,int High){
    li[++tail].Init(from,to,head[from],w,High);
    head[from]=tail;
}

int n,m;

struct Node{
    int ls,rs;
}p[N*40];
int tot;
int Root[N],fa[N*40],Dep[N*40],MinDis[N*40],rtt;
int Hi[N],d[N],Q,K,S;
ll Ans;
bool vis[N];

#define ls(k) p[k].ls
#define rs(k) p[k].rs
struct DSU{
    inline int NewNode(){return ++tot;}
    inline void Build(int &k,int l,int r){
        k=NewNode();
        if(l==r){fa[k]=l;MinDis[k]=d[l];return;}
        int mid=(l+r)>>1;Build(ls(k),l,mid);Build(rs(k),mid+1,r);
    }
    inline int GetPosi(int k,int l,int r,int w){
        if(l==r) return k;
        int mid=(l+r)>>1;
        if(w<=mid) return GetPosi(ls(k),l,mid,w);
        else return GetPosi(rs(k),mid+1,r,w);
    }
    inline int Find(int k,int x){
        // printf("k=%lld x=%lld\n",k,x);
        int now=GetPosi(k,1,n,x);
        // printf("now=%lld\n",now);
        // printf("fa[%lld]=%lld\n",now,fa[now]);
        if(fa[now]==x) return now;
        else return Find(k,fa[now]);
    }
    inline void Change(int &k,int last,int l,int r,int w,int x){
        k=NewNode();
        p[k]=p[last];
        if(l==r){
            Dep[k]=Dep[last];fa[k]=x;MinDis[k]=MinDis[last];
            return;
        }
        int mid=(l+r)>>1;
        if(w<=mid) Change(ls(k),ls(last),l,mid,w,x);
        else Change(rs(k),rs(last),mid+1,r,w,x);
    }
    inline void Update(int &k,int last,int l,int r,int w){
        k=NewNode();
        p[k]=p[last];
        if(l==r){MinDis[k]=MinDis[last];fa[k]=fa[last];Dep[k]=Dep[last]+1;return;}
        int mid=(l+r)>>1;
        if(w<=mid) Update(ls(k),ls(last),l,mid,w);
        else Update(rs(k),rs(last),mid+1,r,w);
    }
    inline void Update2(int &k,int last,int l,int r,int w,int x){
        k=NewNode();
        p[k]=p[last];
        if(l==r){MinDis[k]=x;fa[k]=fa[last];Dep[k]=Dep[last];return;}
        int mid=(l+r)>>1;
        if(w<=mid) Update2(ls(k),ls(last),l,mid,w,x);
        else Update2(rs(k),rs(last),mid+1,r,w,x);
    }
    inline void Merge(int &k,int last,int a,int b){
        int faa=Find(last,a),fab=Find(last,b);
        if(fa[faa]==fa[fab]) return;
        if(Dep[faa]>Dep[fab]) swap(faa,fab);
        Change(k,last,1,n,fa[faa],fa[fab]);
        if(Dep[faa]==Dep[fab]) Update(k,k,1,n,fa[fab]);
        if(MinDis[faa]<MinDis[fab]) Update2(k,k,1,n,fa[fab],MinDis[faa]);
    }
}dsu;

int t;

inline void Init(){
    read(n);read(m);
    for(int i=1;i<=m;i++){
        int from,to,w,h;read(from);read(to);read(w);read(h);
        Add(from,to,w,h);Add(to,from,w,h);
    }
}
//over

struct node{
    int id,val;
    inline node(){}
    inline node(int id,int val) : id(id),val(val) {}
    inline bool operator < (const node &b)const{return val>b.val;}
};
priority_queue<node> q;

inline void Dij(int s){
    // printf("enter\n");
    memset(d,INF,sizeof(d));
    q.push(node(s,0));d[s]=0;
    while(q.size()){
        node top=q.top();q.pop();
        if(vis[top.id]) continue;
        vis[top.id]=1;
        for(int x=head[top.id];x;x=li[x].next){
            int to=li[x].to,w=li[x].w;
            if(d[to]<=d[top.id]+w) continue;
            d[to]=d[top.id]+w;
            q.push(node(to,d[to]));
        }
    }
}
//over

inline void Solve(){
    // printf("enter\n");
    dsu.Build(Root[0],1,n);
    // printf("tot=%lld\n",tot);
    sort(li+1,li+tail+1);
    // printf("now!!!\n");
    for(int i=1,j=1;i<=tail;i=j){
        // if(i>=130000) printf("i=%d\n",i);
        rtt++;int last=Root[rtt-1];Root[rtt]=Root[rtt-1];
        // printf("tot=%lld\n",tot);
        while(li[j].High==li[i].High&&j<=tail){
            dsu.Merge(Root[rtt],last,li[j].from,li[j].to);
            last=Root[rtt];j++;
        }
        Hi[rtt]=li[i].High;
    }
    // printf("tot=%lld\n",tot);
    // printf("rtt=%lld\n",rtt);
    // for(int i=1;i<=rtt;i++) printf("Hi[%lld]=%lld\n",i,Hi[i]);
    // for(int i=1;i<=rtt;i++) printf("Root[%lld]=%lld\n",i,Root[i]);
    // for(int i=1;i<=tot;i++){
    //     if(p[i].ls) printf("%lld %lld\n",i,p[i].ls);
    //     if(p[i].rs) printf("%lld %lld\n",i,p[i].rs);
    // }
    // for(int i=1;i<=tot;i++){
    //     printf("fa[%lld]=%lld\n",i,fa[i]);
    //     printf("MinDis[%lld]=%lld\n",i,MinDis[i]);
    // }
}

inline int Binary(int High){
    int l=1,r=rtt,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(Hi[mid]>High){
            ans=mid;l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}

inline void Clear(){
    tail=0;for(int i=1;i<=n;i++) head[i]=0;
    for(int i=1;i<=tot;i++){fa[i]=Dep[i]=0;MinDis[i]=INF;}
    tot=0;
    for(int i=1;i<=n;i++) Root[i]=0;rtt=tail=0;
    Ans=0;for(int i=1;i<=n;i++) vis[i]=0;
    for(int i=1;i<=n;i++) Hi[i]=0;
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(t);
    memset(MinDis,INF,sizeof(MinDis));
    while(t--){
        // printf("here\n");
        Init();Dij(1);
        // printf("here\n");
        Solve();
        // printf("here\n");
        read(Q);read(K);read(S);
        for(int i=1;i<=Q;i++){
            // printf("i=%d\n",i);
            int V,P;read(V);read(P);
            V=(V+K*Ans-1)%n+1;
            P=(P+K*Ans)%(S+1);
            // printf("P=%lld\n",P);
            int rt=Binary(P);
            // printf("rt=%lld\n",rt);
            int now=dsu.Find(Root[rt],V);
            // printf("V=%lld\n",V);
            Ans=MinDis[now];
            printf("%lld\n",Ans);
        }
        Clear();
    }
    return 0;
}

Kruskal 重构树

如果不用可持久化并查集做下面这个题的话思维难度增加了不少,但也许是我刚刚学 Kruskal 重构树的原因,但是至少不用这么麻烦的调试代码了。

下面先简单的介绍一下 Kruskal 重构树,这个树本质上是为了很快的求出最小瓶颈路径,但是我们可以维护一些多余信息。

何为最小瓶颈路径,也就是两个点之间的简单路径最大值最小的那条路径。

我们考虑这样建立一棵树,我们尽心 Kruskal 算法,对于两个不同连通块的节点,我们新建一个节点,然后从这个新节点像原来的两个点连边,令这个点的点权等于原来那条边的边权,然后我们考察两个点 lca 处点的点权,就是这两个点的最小瓶颈路径上的最大边权。

我们考虑这棵树还有以下性质:

  • 是一颗二叉树。
  • 是一个大根堆。
  • 我们上面说的最小瓶颈的性质。

然后我们考虑这样做的遍历,当给定边权限制的时候,我们可以很方便的求出每个点与之相连的点集。

T1

链接

这道题不难想到用可持久化并查集做,但是我们考虑如何用 Kruskal 重构树来做。

首先,不难发现最终答案和 Kruskal 有一定关系,但是有可能答案比 Kruskal 大,而且我们在做 Kruskal 重构树的时候维护点集当前图是否为一条链。我们这样来做:

我们按照边权从小到大来做,然后我们考虑对于当前边两端的连通块,如果两端有一个不是链,那么我们可以把这条边的权值挂在这个点上,如果两端都是链,连起来之后不是,我们仍然这样做,否则我们什么都不做,维护这条链就行。如果一条边两边点属于同一个连通块,我们就更新当前 lca 上的答案即可。

然后我们从上面往下 dfs,如果父亲有值,儿子没有值,我们直接覆盖。

考虑这么做的正确性,当一个两个连通块合并为非链的时刻,当前的权值一定是最优的,否则不符合 Kruskal 重构树的性质,如果一直是链,到某一条边的时候不是链了,那么之前的答案也一定是这条边的权值。这就是为什么我们要把答案往下推。

考虑维护链,我们只需要记一下链的端点,然后打一个 Tag 表示当前是否为链。

然后我们直接树剖,求 lca 就可以了。

代码:

    #include<bits/stdc++.h>
// #include"swap.h"
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
using namespace std;

const int maxn=200010;
const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct edge{
    int from,to,next,w;
    inline void Init(int fr_,int to_,int ne_){
        from=fr_;to=to_;next=ne_;
    }
    inline bool operator < (const edge &b)const{return w<b.w;}
}li[maxn<<2],li2[maxn<<2];
int head[maxn],tail;

inline void Add(int from,int to){
    li[++tail].Init(from,to,head[from]);
    head[from]=tail;
}

int fa[maxn<<1],now,n,m;
int Ans[maxn<<1],P[maxn<<1][2],Tag[maxn<<1];

inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}

inline bool IsNotThePoint(int p,int top){
    return p!=P[top][0]&&p!=P[top][1];
}

inline int OtherPoint(int p,int top){
    if(p==P[top][0]) return P[top][1];
    else return P[top][0];
}

inline void dfs(int k){
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(Ans[k]&&!Ans[to]) Ans[to]=Ans[k];
        dfs(to);
    }
}

int Top[maxn],Dep[maxn],Size[maxn],Son[maxn],Fa[maxn];

inline void dfs1(int k,int fat){
    Dep[k]=Dep[fat]+1;Size[k]=1;Fa[k]=fat;
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        dfs1(to,k);Size[k]+=Size[to];
        if(Size[Son[k]]<Size[to]) Son[k]=to;
    }
}

inline void dfs2(int k,int t){
    Top[k]=t;
    if(Son[k]) dfs2(Son[k],t);
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(to==Son[k]) continue;
        dfs2(to,to);
    }
}

inline int GetLca(int a,int b){
    while(Top[a]!=Top[b]){
        if(Dep[Top[a]]<Dep[Top[b]]) swap(a,b);
        a=Fa[Top[a]];
    }
    if(Dep[a]>Dep[b]) swap(a,b);
    return a;
}

inline void Kruskal(){
    sort(li2+1,li2+m+1);
    for(int i=1;i<=(n<<1);i++){
        fa[i]=i;Tag[i]=-1;P[i][0]=P[i][1]=i;
    }
    for(int i=1;i<=m;i++){
        int faa=Find(li2[i].from),fab=Find(li2[i].to);
        if(faa==fab){
            if(Ans[faa]) continue;
            Tag[faa]=2;Ans[faa]=li2[i].w;
        }   
        else{
            now++;
            fa[faa]=now;fa[fab]=now;Add(now,faa);Add(now,fab);
            if(Tag[faa]==2||Tag[fab]==2||(IsNotThePoint(li2[i].from,faa)||IsNotThePoint(li2[i].to,fab))){
                if(Ans[now]) continue;
                Ans[now]=li2[i].w;Tag[now]=2;
            }
            else{
                P[now][0]=OtherPoint(li2[i].from,faa);
                P[now][1]=OtherPoint(li2[i].to,fab);
                Tag[now]=1;
            }
        } 
    }
    dfs(now);dfs1(now,0);dfs2(now,now);
}

void init(int N, int M, std::vector<int> U, std::vector<int> V, std::vector<int> W){
    n=N;m=M;
    for(int i=1;i<=M;i++){
        li2[i].from=U[i-1];li2[i].to=V[i-1];li2[i].w=W[i-1];
        li2[i].from++;li2[i].to++;
    }
    now=N;Kruskal();
}

int getMinimumFuelCapacity(int X, int Y){
    X++;Y++;
    int lca=GetLca(X,Y);
    return (!Ans[lca])?-1:Ans[lca];
}

// inline void Init(){
//     read(n);read(m);
//     for(int i=1;i<=m;i++){
//         read(li2[i].from);read(li2[i].to);read(li2[i].w);
//         li2[i].from++;li2[i].to++;
//     }
//     now=n;Kruskal();
// }

// int main(){
//     freopen("my.in","r",stdin);
//     freopen("my.out","w",stdout);
//     Init();
//     int Q;read(Q);
//     for(int i=1;i<=Q;i++){
//         int a,b;read(a);read(b);
//         a++;b++;
//         printf("%d\n",getMinimumFuelCapacity(a,b));
//     }
//     return 0;
// }

可持久化字典树

T1

链接

这个题目的难点在于想到我们按照右端点分类,然后我们预先处理出每个前缀的最大值,取出最大值后可以对区间进行分裂。这个想法非常妙,最大值维护次大值。

因为要维护区间异或最大值,所以我们要用可持久化字典树。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 20020010
#define M 500010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct Node{
    int ch[2],Size,End;
}p[N];

int tot,Root[N],rtt;

typedef pair<int,int> P;

struct Trie{
    inline Trie(){memset(p,0,sizeof(p));tot=0;}
    inline void Insert(int last,int x,int id){
        Root[++rtt]=++tot;
        int now=tot,q=last;
        for(int i=33;i>=0;i--){
            int w=(x>>i)&1;
            p[now]=p[q];
            p[now].ch[w]=++tot;
            p[now].Size=p[q].Size+1;
            now=p[now].ch[w];
            q=p[q].ch[w];
        }
        p[now].End=id;p[now].Size=p[q].Size+1;
    }
    inline P Query(int l,int r,int x){
        if(r<l) return make_pair(0,0);
        int res=0,q=Root[l-1],now=Root[r];
        for(int i=33;i>=0;i--){
            int w=(x>>i)&1;
            if(p[p[now].ch[w^1]].Size-p[p[q].ch[w^1]].Size>0){
                now=p[now].ch[w^1];q=p[q].ch[w^1];res|=(1ll<<i);
            }
            else now=p[now].ch[w],q=p[q].ch[w];
        }
        return make_pair(res,p[now].End);
    }
}tr;

int n,k,a[M],Ans;

struct node{
    int val,k,l,r,sum;
    inline node(){}
    inline node(int val,int k,int l,int r,int sum) : val(val),k(k),l(l),r(r),sum(sum) {}
    inline bool operator < (const node &b)const{return val<b.val;}
};
priority_queue<node> q;

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(k);
    for(int i=2;i<=n+1;i++) read(a[i]);
    tr.Insert(0,0,1);
    for(int i=2;i<=n+1;i++){
        a[i]^=a[i-1];tr.Insert(Root[rtt],a[i],i);
    }
    for(int i=2;i<=n+1;i++){
        P val=tr.Query(1,i,a[i]);
        // printf("val.first=%lld\n",val.first);
        q.push(node(val.first,val.second,1,i,a[i]));
    }
    for(int i=1;i<=k;i++){
        node ans=q.top();q.pop();Ans+=ans.val;
        // printf("ans.val=%lld\n",ans.val);
        // printf("ans.sum=%lld\n",ans.sum);
        // printf("ans.k=%lld\n",ans.k);
        P Max1=tr.Query(ans.l,ans.k-1,ans.sum);
        P Max2=tr.Query(ans.k+1,ans.r,ans.sum);
        // printf("Max1.f=%lld Max1.se=%lld\n",Max1.first,Max1.second);
        // printf("Max2.f=%lld Max2.se=%lld\n",Max2.first,Max2.second);
        q.push(node(Max1.first,Max1.second,ans.l,ans.k-1,ans.sum));
        q.push(node(Max2.first,Max2.second,ans.k+1,ans.r,ans.sum));
    }
    printf("%lld\n",Ans);
    return 0;
}
posted @ 2022-03-22 11:18  hyl天梦  阅读(79)  评论(0编辑  收藏  举报