数据结构
STL
-
向量/栈/队列/优先队列/集合/映射/可重集。
-
对顶堆解决中位数相关问题。
详解
使用大顶堆维护较小的一半元素,小顶堆维护另一半,时刻保持二者元素个数之差不超过 \(1\)。
Code
#include<iostream> #include<queue> #include<vector> #include<functional> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; priority_queue<int,vector<int>,less<int>>max_heap; priority_queue<int,vector<int>,greater<int>>min_heap; int n,tmp; int main() { IOS; cin>>n; for(int i=1;i<=n;i++) { cin>>tmp; if(!max_heap.empty()&&tmp>max_heap.top()) min_heap.push(tmp); else max_heap.push(tmp); if(max_heap.size()>min_heap.size()+1) min_heap.push(max_heap.top()),max_heap.pop(); if(min_heap.size()>max_heap.size()+1) max_heap.push(min_heap.top()),min_heap.pop(); if(i&1) cout<<(max_heap.size()>min_heap.size()?max_heap.top():min_heap.top())<<'\n'; } return 0; } -
会启发式合并。
详解
在涉及数组/集合/映射的合并中,尤其是在树上自底向上合并时常见,总是将小的向大的合并,可以实现均摊 \(O(n \log n)\) 的时间复杂度。
需要注意的是,由于合并过程可能会发生 swap 操作,因此最终留在节点 \(u\) 上的容器,内部所保存的值并不一定反映该节点的情况,故需要 merge 完成后及时求解当前节点 \(u\) 的答案。
Code
#include<iostream> #include<map> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e5+5; int n,head[N],nxt[N<<1],to[N<<1],cnt=0,tot[N]; long long ans[N]; map<int,int>col[N]; void Add(int u,int v) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; } void Dfs(int u,int fa) { for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==fa) continue; Dfs(v,u); if(col[u].size()<col[v].size()) { tot[u]=tot[v];//v上的答案已经保存过了,故不需要交换 ans[u]=ans[v]; swap(col[u],col[v]); } for(auto tmp:col[v])//把v合并到u上 { int c=tmp.first,num=tmp.second; col[u][c]+=num; if(col[u][c]>tot[u]) tot[u]=col[u][c],ans[u]=c; else if(col[u][c]==tot[u]) ans[u]+=c; } } } int main() { IOS; cin>>n; for(int i=1;i<=n;i++) { int tmp; cin>>tmp; tot[i]=1,col[i][tmp]=1,ans[i]=tmp; } for(int i=1;i<n;i++) { int u,v; cin>>u>>v; Add(u,v),Add(v,u); } Dfs(1,0); for(int i=1;i<=n;i++) cout<<ans[i]<<' '; return 0; }
二分
- 将最优解转化为存在解,需满足单调性。
双指针
- 维护左右两处指针,需满足右指针向后移动时左指针同样单调移动。
单调栈
-
维护左侧/右侧最近邻满足某种偏序的所在位置。
详解
通过维护单调递增或递减的栈,求解如左侧/右侧第一个大于/小于该位置上的元素。
Code
#include<iostream> #include<stack> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e6+5; stack<int>stk; int n,h[N],v[N],ans[N],maxx; int main() { IOS; cin>>n; for(int i=1;i<=n;i++) cin>>h[i]>>v[i]; for(int i=1;i<=n;i++) { while(!stk.empty()&&h[i]>h[stk.top()]) ans[i]+=v[stk.top()],maxx=max(maxx,ans[i]),stk.pop(); if(!stk.empty()) ans[stk.top()]+=v[i],maxx=max(maxx,ans[stk.top()]); stk.push(i); } cout<<maxx<<'\n'; return 0; } -
常用于在钦定某处取得最值时,寻找最大合法的左右范围。
详解
即求以某元素为最大/最小值时,能扩展的最大区间。
Code
#include<iostream> #include<stack> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e5+5; int n,a[N],f[N],g[N]; long long ans=0; stack<int>stk; int main() { IOS; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) { while(!stk.empty()&&a[i]<a[stk.top()]) stk.pop(); if(!stk.empty()) f[i]=stk.top(); stk.push(i); } while(!stk.empty()) stk.pop(); for(int i=n;i>=1;i--) { while(!stk.empty()&&a[i]<a[stk.top()]) stk.pop(); if(!stk.empty()) g[i]=stk.top(); else g[i]=n+1; stk.push(i); } for(int i=1;i<=n;i++) ans+=1ll*a[i]*(i-f[i])*(g[i]-i); for(int i=1;i<=n;i++) cout<<f[i]<<' '<<g[i]<<'\n'; cout<<ans<<'\n'; return 0; }
单调队列
-
维护连续区间内的最值。
详解
维护一个双端队列,使其内部元素保持单增/单减。
需要注意的是,在滑动窗口这类给定区间长度的题目中,不能简单地用队列大小是否大于区间长度来判断队首元素是否过时。
Code
#include<iostream> #include<queue> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e6+5; int n,k,a[N]; deque<int>q; int main() { IOS; cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) { while(!q.empty()&&a[i]<a[q.back()]) q.pop_back(); q.push_back(i); if(q.front()<i-k+1)//弹出过时元素 q.pop_front(); if(i>=k) cout<<a[q.front()]<<' '; } cout<<'\n'; while(!q.empty()) q.pop_back(); for(int i=1;i<=n;i++) { while(!q.empty()&&a[i]>a[q.back()]) q.pop_back(); q.push_back(i); if(q.front()<i-k+1) q.pop_front(); if(i>=k) cout<<a[q.front()]<<' '; } return 0; } -
要求给定区间长或区间右侧向右时左侧单调向右。
-
会单调队列优化DP
详解
使用单调队列快速查询可以转移至当前状态的最优解,并及时清理过时元素
Code
#include<iostream> #include<queue> #include<cstring> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=2e5+5; int n,L,R,a[N],dp[N],maxx=-0x3f3f3f3f; deque<int>q; int main() { IOS; memset(dp,0xaf,sizeof(dp)); //有些位置不存在到达的可能,故需初始化 cin>>n>>L>>R; for(int i=0;i<=n;i++) cin>>a[i]; dp[0]=0; for(int i=L;i<=n;i++) { while(!q.empty()&&dp[i-L]>dp[q.back()]) q.pop_back(); q.push_back(i-L); while(!q.empty()&&q.front()<i-R) q.pop_front(); if(!q.empty()) dp[i]=dp[q.front()]+a[i]; if(i+R>n) maxx=max(maxx,dp[i]); } cout<<maxx<<'\n'; return 0; }
前缀和
-
静态维护连续区间和/异或和等具有逆运算的区间答案。
详解
前缀和可以实现 \(O(1)\) 查询静态区间和
\(pre_i = pre_{i-1} + a_i\),\(\Sigma_{i=l}^r a_i = pre_r - pre_{l-1}\)
\(xor_i = xor_{i-1} \oplus a_i\),\(\bigoplus_{i=l}^{r} a_i = xor_r \oplus pre_{l-1}\)
-
高维下利用容斥完成,例如二维前缀和。
详解
\(pre_{i,j} = pre_{i-1,j} + pre_{i,j-1} - pre_{i-1,j-1} + a_{i,j}\)
从 \((a,b)\) 到 \((x,y)\) 的矩阵和:
\(sum = pre_{x,y} - pre_{a-1,y} - pre{x,b-1} + pre_{a-1,b-1}\)Code
#include<iostream> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=105; int n,m,a[N][N],pre[N][N],ans; int Calc(int i1,int j1,int i2,int j2) { return pre[i2][j2]-pre[i1-1][j2]-pre[i2][j1-1]+pre[i1-1][j1-1]; } bool Check(int x) { for(int i=1;i<=n-x+1;i++) for(int j=1;j<=m-x+1;j++) if(Calc(i,j,i+x-1,j+x-1)==x*x) return true; return false; } int main() { IOS; cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j],pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j]; int L=1,R=n; while(L<=R) { int Mid=(L+R)>>1; if(Check(Mid)) ans=Mid,L=Mid+1; else R=Mid-1; } cout<<ans<<'\n'; return 0; } -
了解差分、原数组、前缀和之间的相互关系并可推导。
详解
$pre_i = pre_{i-1} + a_i,diff_i = a_i - a_{i-1} $
\(a_i = \Sigma_{j=1}^i diff_j\),\(pre_i = \Sigma_{j=1}^i \Sigma_{k=1}^j diff_k = \Sigma_{j=1}^i (i-j+1) diff_j\)
差分
-
区间修改转为头尾差分。
详解
差分可以实现 \(O(1)\) 修改,查询前 \(O(n)\) 计算
对区间 \([L,R]\) 每个元素加上 \(val\):
\(diff_L += val,diff_{R+1} -= val\)
-
树上路径修改转为节点差分。
点差分
对 \(u,v\) 路径上每个点权加上 \(val\):
\(diff_u +=val,diff_v += val,diff_{lca} -=val,diff_{father_{lca}} -= val\)
Code
#include<iostream> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=5e4+5; const int LogN=20; int n,k,head[N],nxt[N<<1],to[N<<1],cnt=0,dep[N],father[N][LogN],diff[N],num[N],maxx; void Add(int u,int v) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; } void Dfs_pre(int u,int fa) { dep[u]=dep[fa]+1,father[u][0]=fa; for(int i=1;i<LogN;i++) father[u][i]=father[father[u][i-1]][i-1]; for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==fa) continue; Dfs_pre(v,u); } } int LCA(int u,int v) { if(dep[u]<dep[v]) swap(u,v); for(int i=LogN-1;i>=0;i--) if(dep[father[u][i]]>=dep[v]) u=father[u][i]; if(u==v) return u; for(int i=LogN-1;i>=0;i--) if(father[u][i]!=father[v][i]) u=father[u][i],v=father[v][i]; return father[u][0]; } void Dfs_calc(int u,int fa) { num[u]=diff[u]; for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==fa) continue; Dfs_calc(v,u); num[u]+=num[v]; } } int main() { IOS; cin>>n>>k; for(int i=1;i<n;i++) { int u,v; cin>>u>>v; Add(u,v),Add(v,u); } Dfs_pre(1,0); while(k--) { int s,t,tmp; cin>>s>>t; tmp=LCA(s,t); diff[s]++,diff[t]++; diff[tmp]--,diff[father[tmp][0]]--; } Dfs_calc(1,0); for(int i=1;i<=n;i++) maxx=max(maxx,num[i]); cout<<maxx<<'\n'; return 0; }边差分
对 \(u,v\) 路径上每个边权加上 \(val\),把每条边权记在深度更大的节点上:
\(diff_u += val,diff_v += val,diff_{lca} -= 2 \times val\)
注意此时 \(lca\) 上存的边权并不在 \(u,v\) 路径包含范围内,故不应统计在内,同时如果用树链剖分计算路径长度,那么当 \(u,v\) 跳到同一条链之后的查询应做出改变,不包含更浅的节点上存的值
Code
#include<iostream> #include<cstring> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=3e5+5; int n,m,head[N],nxt[N<<1],to[N<<1],w[N<<1],cnt=0,dep[N],father[N],siz[N],son[N],dfn[N],seg[N],top[N],idx=0,val[N],diff[N],sum[N],maxx,ans; struct Data { int st,ed,co; Data(int _st=0,int _ed=0,int _co=0):st(_st),ed(_ed),co(_co){} }a[N]; struct Segment_Tree { int Tree[N<<2]; void Pushup(int k) { Tree[k]=Tree[k<<1]+Tree[k<<1|1]; } void Buildtree(int k,int L,int R) { if(L==R) { Tree[k]=val[seg[L]]; return; } int Mid=(L+R)>>1; Buildtree(k<<1,L,Mid); Buildtree(k<<1|1,Mid+1,R); Pushup(k); } int Query(int k,int L,int R,int ql,int qr) { if(ql>qr) return 0; if(ql<=L&&R<=qr) return Tree[k]; int Mid=(L+R)>>1,res=0; if(ql<=Mid) res=res+Query(k<<1,L,Mid,ql,qr); if(qr>Mid) res=res+Query(k<<1|1,Mid+1,R,ql,qr); return res; } }Segment; void Dfs_pre(int u,int fa) { dep[u]=dep[fa]+1,father[u]=fa,siz[u]=1; for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==fa) continue; val[v]=w[i]; Dfs_pre(v,u); siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } void Dfs_split(int u,int new_top) { if(!u) return; dfn[u]=++idx,seg[idx]=u,top[u]=new_top; Dfs_split(son[u],new_top); for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==father[u]||v==son[u]) continue; Dfs_split(v,v); } } int Query_path(int u,int v) { int res=0; while(top[u]!=top[v]) { if(dep[top[u]]>=dep[top[v]]) res=res+Segment.Query(1,1,n,dfn[top[u]],dfn[u]),u=father[top[u]]; else res=res+Segment.Query(1,1,n,dfn[top[v]],dfn[v]),v=father[top[v]]; } if(dep[u]>=dep[v]) res=res+Segment.Query(1,1,n,dfn[v]+1,dfn[u]);//注意此处必须+1,因为v上的点权所对应的边不在v->u之内 else res=res+Segment.Query(1,1,n,dfn[u]+1,dfn[v]); return res; } int LCA(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>=dep[top[v]]) u=father[top[u]]; else v=father[top[v]]; } if(dep[u]>=dep[v]) return v; else return u; } void Add(int u,int v,int cost) { to[++cnt]=v; w[cnt]=cost; nxt[cnt]=head[u]; head[u]=cnt; } void Dfs_calc(int u,int fa) { sum[u]+=diff[u]; for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(v==fa) continue; Dfs_calc(v,u); sum[u]+=sum[v]; } } int main() { IOS; cin>>n>>m; for(int i=1;i<n;i++) { int u,v,w; cin>>u>>v>>w; Add(u,v,w),Add(v,u,w); } Dfs_pre(1,0); Dfs_split(1,1); Segment.Buildtree(1,1,n); for(int i=1;i<=m;i++) { int s,t,tmp; cin>>s>>t; tmp=Query_path(s,t); maxx=max(maxx,tmp); a[i]={s,t,tmp}; } int L=0,R=maxx+1; while(L<=R) { memset(diff,0,sizeof(diff)); memset(sum,0,sizeof(sum)); int Mid=(L+R)>>1,tot=0; for(int i=1;i<=m;i++) { if(a[i].co>Mid) { tot++; diff[a[i].st]+=1,diff[a[i].ed]+=1; diff[LCA(a[i].st,a[i].ed)]-=2; } } if(tot==0) { ans=Mid,R=Mid-1; continue; } Dfs_calc(1,0); bool flag=false; for(int i=1;i<=n;i++) { if(sum[i]==tot&&maxx-val[i]<=Mid) { flag=true; break; } } if(flag) ans=Mid,R=Mid-1; else L=Mid+1; } cout<<ans<<'\n'; return 0; }
ST表
-
静态区间最值/区间GCD等问题。运算对象需满足可并性,且重叠不影响求解。
详解
用倍增的思想解决可重复贡献的区间最值/GCD/LCM/按位与/按位或问题Code
#include<iostream> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e5+5; const int LogN=20; int n,m,a[N],f[N][LogN],Log[N]; int main() { IOS; cin>>n>>m; Log[0]=-1; for(int i=1;i<=n;i++) cin>>a[i],Log[i]=Log[i>>1]+1,f[i][0]=a[i]; for(int j=1;j<=Log[n];j++) for(int i=1;i+(1<<j)-1<=n;i++) f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); while(m--) { int L,R; cin>>L>>R; int k=Log[R-L+1]; cout<<max(f[L][k],f[R-(1<<k)+1][k])<<'\n'; } return 0; }
树状数组
在线单次 \(O(\log n)\) 维护区间和等具有逆运算的区间答案。
通过加一个log的复杂度可以维护区间最值等问题。
线段树
-
对满足幺半群的运算会建立线段树。
-
会对多标记的优先级进行分析。
Code
#include<iostream> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e5+5; int n,Q,MOD,a[N]; struct Segment_tree { long long Tree[N<<2],add_tag[N<<2],mul_tag[N<<2]; void Pushup(int k) { Tree[k]=(Tree[k<<1]+Tree[k<<1|1])%MOD; } void Buildtree(int k,int L,int R) { mul_tag[k]=1; if(L==R) { Tree[k]=a[L]%MOD; return; } int Mid=(L+R)>>1; Buildtree(k<<1,L,Mid); Buildtree(k<<1|1,Mid+1,R); Pushup(k); } void Pushdown(int k,int L,int R) { int Mid=(L+R)>>1; if(mul_tag[k]!=1) { Tree[k<<1]=Tree[k<<1]*mul_tag[k]%MOD; mul_tag[k<<1]=mul_tag[k<<1]*mul_tag[k]%MOD; add_tag[k<<1]=add_tag[k<<1]*mul_tag[k]%MOD; Tree[k<<1|1]=Tree[k<<1|1]*mul_tag[k]%MOD; mul_tag[k<<1|1]=mul_tag[k<<1|1]*mul_tag[k]%MOD; add_tag[k<<1|1]=add_tag[k<<1|1]*mul_tag[k]%MOD; } if(add_tag[k]!=0) { Tree[k<<1]=(Tree[k<<1]+add_tag[k]*(Mid-L+1)%MOD)%MOD;//记得乘上区间长度 add_tag[k<<1]=(add_tag[k<<1]+add_tag[k])%MOD; Tree[k<<1|1]=(Tree[k<<1|1]+add_tag[k]*(R-Mid)%MOD)%MOD; add_tag[k<<1|1]=(add_tag[k<<1|1]+add_tag[k])%MOD; } mul_tag[k]=1,add_tag[k]=0; } void Modify_add(int k,int L,int R,int ql,int qr,long long val) { if(ql<=L&&R<=qr) { Tree[k]=(Tree[k]+val*(R-L+1)%MOD)%MOD; add_tag[k]=(add_tag[k]+val)%MOD; return; } Pushdown(k,L,R); int Mid=(L+R)>>1; if(ql<=Mid) Modify_add(k<<1,L,Mid,ql,qr,val); if(qr>Mid) Modify_add(k<<1|1,Mid+1,R,ql,qr,val); Pushup(k); } void Modify_mul(int k,int L,int R,int ql,int qr,long long val) { if(ql<=L&&R<=qr) { Tree[k]=Tree[k]*val%MOD; mul_tag[k]=mul_tag[k]*val%MOD; add_tag[k]=add_tag[k]*val%MOD; return; } Pushdown(k,L,R); int Mid=(L+R)>>1; if(ql<=Mid) Modify_mul(k<<1,L,Mid,ql,qr,val); if(qr>Mid) Modify_mul(k<<1|1,Mid+1,R,ql,qr,val); Pushup(k); } long long Query(int k,int L,int R,int ql,int qr) { if(ql<=L&&R<=qr) return Tree[k]; Pushdown(k,L,R); long long res=0; int Mid=(L+R)>>1; if(ql<=Mid) res=(res+Query(k<<1,L,Mid,ql,qr))%MOD; if(qr>Mid) res=(res+Query(k<<1|1,Mid+1,R,ql,qr))%MOD; return res; } }Segment; int main() { IOS; cin>>n>>Q>>MOD; for(int i=1;i<=n;i++) cin>>a[i]; Segment.Buildtree(1,1,n); while(Q--) { int opt,L,R; long long val; cin>>opt; if(opt==1) cin>>L>>R>>val,Segment.Modify_mul(1,1,n,L,R,val); else if(opt==2) cin>>L>>R>>val,Segment.Modify_add(1,1,n,L,R,val); else cin>>L>>R,cout<<Segment.Query(1,1,n,L,R)<<'\n'; } return 0; } -
会进行线段树上二分。
-
会使用权值线段树,会离散化后维护。
Code
#include<iostream> #include<algorithm> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=1e5+5; struct Segment_Tree { int Tree[N<<2]; void Pushup(int k) { Tree[k]=Tree[k<<1]+Tree[k<<1|1]; } void Modify(int k,int L,int R,int pos,int val) { if(L==R) { Tree[k]+=val; return; } int Mid=(L+R)>>1; if(pos<=Mid) Modify(k<<1,L,Mid,pos,val); if(pos>Mid) Modify(k<<1|1,Mid+1,R,pos,val); Pushup(k); } int Query(int k,int L,int R,int ql,int qr) { if(ql>qr) return 0;//注意边界问题 if(ql<=L&&R<=qr) return Tree[k]; int res=0,Mid=(L+R)>>1; if(ql<=Mid) res=res+Query(k<<1,L,Mid,ql,qr); if(qr>Mid) res=res+Query(k<<1|1,Mid+1,R,ql,qr); return res; } int Query_val(int k,int L,int R,int rank) { if(L==R) return L; int Mid=(L+R)>>1; if(rank<=Tree[k<<1]) return Query_val(k<<1,L,Mid,rank); else return Query_val(k<<1|1,Mid+1,R,rank-Tree[k<<1]); } int Predecessor(int siz,int val) { int cnt=Query(1,1,siz,1,val-1); if(cnt==0) return 0; return Query_val(1,1,siz,cnt); } int Successor(int siz,int val) { int cnt=Query(1,1,siz,1,val); if(cnt==Tree[1]) return 0; return Query_val(1,1,siz,cnt+1); } }Segment; struct Node { int opt; int x; }Ques[N]; int n,siz,Hash[N]; int main() { IOS; cin>>n; for(int i=1;i<=n;i++) { cin>>Ques[i].opt>>Ques[i].x; if(Ques[i].opt!=4) Hash[++siz]=Ques[i].x; } sort(Hash+1,Hash+siz+1); siz=unique(Hash+1,Hash+siz+1)-(Hash+1); for(int i=1;i<=n;i++) { if(Ques[i].opt==4) continue; Ques[i].x=lower_bound(Hash+1,Hash+siz+1,Ques[i].x)-Hash; } for(int i=1;i<=n;i++) { if(Ques[i].opt==1) Segment.Modify(1,1,siz,Ques[i].x,1); else if(Ques[i].opt==2) Segment.Modify(1,1,siz,Ques[i].x,-1); else if(Ques[i].opt==3) cout<<Segment.Query(1,1,siz,1,Ques[i].x-1)+1<<'\n'; else if(Ques[i].opt==4) cout<<Hash[Segment.Query_val(1,1,siz,Ques[i].x)]<<'\n'; else if(Ques[i].opt==5) cout<<Hash[Segment.Predecessor(siz,Ques[i].x)]<<'\n'; else cout<<Hash[Segment.Successor(siz,Ques[i].x)]<<'\n'; } return 0; } -
会动态开点线段树。
-
标记永久化:只需要打标记无需下放,标记只会增加或者只会擦除原有存在标记。
-
利用线段树维护二维偏序和扫描线。
-
会线段树优化DP。
-
会进行可持久化,并利用可持久化后的线段树进行版本diff后求解。
Code
#include<iostream> #include<algorithm> #define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int N=2e5+5; const int LogN=25; struct Segment_Tree { struct Node { int lft,rgt,siz; Node(){lft=0,rgt=0,siz=0;} }Tree[(N<<2)+N*LogN]; int point=0; int Clone(int k) { Tree[++point]=Tree[k]; return point; } void Pushup(int k) { Tree[k].siz=Tree[Tree[k].lft].siz+Tree[Tree[k].rgt].siz; } int Buildtree(int k,int L,int R) { k=Clone(k); if(L==R) return k; int Mid=(L+R)>>1; Tree[k].lft=Buildtree(Tree[k].lft,L,Mid); Tree[k].rgt=Buildtree(Tree[k].rgt,Mid+1,R); Pushup(k); return k; } int Modify(int k,int L,int R,int pos,int val) { k=Clone(k); if(L==R) { Tree[k].siz+=val; return k; } int Mid=(L+R)>>1; if(pos<=Mid) Tree[k].lft=Modify(Tree[k].lft,L,Mid,pos,val); if(pos>Mid) Tree[k].rgt=Modify(Tree[k].rgt,Mid+1,R,pos,val); Pushup(k); return k; } int Query(int A,int B,int L,int R,int rank) { if(L==R) return L; int Mid=(L+R)>>1,lftsiz=Tree[Tree[B].lft].siz-Tree[Tree[A].lft].siz; if(rank<=lftsiz) return Query(Tree[A].lft,Tree[B].lft,L,Mid,rank); else return Query(Tree[A].rgt,Tree[B].rgt,Mid+1,R,rank-lftsiz); } }Segment; int n,m,a[N],Hash[N],siz,root[N]; int main() { IOS; cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i],Hash[i]=a[i]; sort(Hash+1,Hash+n+1); siz=unique(Hash+1,Hash+n+1)-Hash-1; root[0]=Segment.Buildtree(0,1,siz); for(int i=1;i<=n;i++) { a[i]=lower_bound(Hash+1,Hash+siz+1,a[i])-Hash; root[i]=Segment.Modify(root[i-1],1,siz,a[i],1); } while(m--) { int L,R,k; cin>>L>>R>>k; cout<<Hash[Segment.Query(root[L-1],root[R],1,siz,k)]<<'\n'; } return 0; } -
会进行线段树合并。
平衡树
- 会写无旋treap,会求解第k大问题,会解决区间翻转标记。
- 会写可持久化treap。
cdq分治
- 会利用cdq分治解决二维偏序和三维偏序。
分块
- 对序列分块
- 对操作分块(每根号n个操作后更新当前操作对象)
- 块间\(O(n\sqrt{n})\)预处理(区间众数)
- 会使用根号平衡将问题分解为两种情形并分别维护。
莫队
- 普通莫队
- 带修莫队
bitset
- bitset维护图上传递闭包。
- bitset维护01背包。
- bitset维护状态压缩。
树链剖分
- 会进行轻重链剖分,并可基于dfs树将路径分解为\(O(\log n)\)个链更新。
并查集
- 会普通并查集。并会进行路径压缩。
- 会带权并查集。会维护两种关系:集合本身信息/点到集合代表元素间的信息。
- 会拆点维护多重并查集/扩展域并查集。
- 会维护关系并查集。
- 基于并查集维护生成树。
- 会维护最小生成树重构树,并理解节点含义,能对重构树维护树上DP或查询节点关系。
- 若不能路径压缩,会写按秩合并。
- 会写带撤销并查集。
- 会写可持久化并查集。
最近公共祖先
- 基于倍增或树链剖分维护最近公共祖先。
- 基于倍增或树链剖分维护链上信息。
字符串相关
- 会写KMP。
- 会写字典树,包括普通字典树和01字典树。
- 基于01字典树维护异或最值。
- 会写可持久化字典树。
- 会写马拉车求回文串。
哈希
- 会基于进制的哈希。
- 会基于异或的哈希。
- 会基于随机化与和的哈希。
- 会对二叉树、栈上状态等进行哈希。
- 会基于哈希求回文串、基于哈希判断状态相同。
浙公网安备 33010602011771号