正睿——可持久化数据结构刷题笔记
主席树
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;
}