可持久化线段树&主席树
可持久化线段树
注意空间开大10~40倍
模板
修改查询历史版本
其实操作很简单,就root开个数组,然后每次修改旧版本时,边记录旧的,边开一个新的,新的先粘旧的,再修改
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=4e7+10;//开大40倍
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,a[N];
int ls[N],rs[N],val[N],rt[N],cnt;
#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
p=++cnt;
if(l==r) { val[p]=a[l];return; }
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
}
void modify(int l,int r,int pos,int v,int pre,int &p) {
p=++cnt;
ls[p]=ls[pre],rs[p]=rs[pre],val[p]=val[pre];
if(l==r) { val[p]=v;return; }
if(pos<=mid) modify(l,mid,pos,v,ls[pre],ls[p]);
else modify(mid+1,r,pos,v,rs[pre],rs[p]);
}
int query(int l,int r,int pos,int p) {
if(l==r) return val[p];
if(pos<=mid) return query(l,mid,pos,ls[p]);
else return query(mid+1,r,pos,rs[p]);
}
int main() {
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,n,rt[0]);
for(int i=1,pre,op,x,y;i<=m;i++) {
pre=read();op=read();x=read();
if(op==1) {
y=read();
modify(1,n,x,y,rt[pre],rt[i]);
}
else {
printf("%d\n",query(1,n,x,rt[pre]));
rt[i]=rt[pre];
}
}
return 0;
}
主席树
名字来历好像是发明这玩意的人名字缩写和主席一样....
模板
求静态区间第 k 小
首先我们要将所有数字离散化。主席树相当于是在每个位置(每一个前缀)维护了一个线段树,线段树的节点是一个区间\([x, y]\),这里的\(x\)和\(y\)都是离散后 数的编号。
主席树节点中维护的值,是 \(1-i\) 之间这个区间内出现了的数的次数。然后当我们查询的时候,就是利用到了前缀和的思想。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=4e7+10;//开大40倍
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,a[N],b[N],pos;
int ls[N],rs[N],val[N],rt[N],cnt;
#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
p=++cnt;
if(l==r) return;
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
}
void modify(int l,int r,int pos,int &p) {
int x=++cnt;
ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
if(l==r) return;
if(pos<=mid) modify(l,mid,pos,ls[p]);
else modify(mid+1,r,pos,rs[p]);
}
int query(int l,int r,int x,int y,int rk) {
if(l==r) return l;
int num=val[ls[y]]-val[ls[x]];
if(num>=rk) return query(l,mid,ls[x],ls[y],rk);
else return query(mid+1,r,rs[x],rs[y],rk-num);
}
int main() {
n=read();m=read();
for(int i=1;i<=n;i++) b[i]=a[i]=read();
sort(b+1,b+1+n);
int len=unique(b+1,b+1+n)-b-1;
build(1,len,rt[0]);
for(int i=1;i<=n;i++) {
pos=lower_bound(b+1,b+1+len,a[i])-b;
modify(1,len,pos,rt[i]=rt[i-1]);
}
for(int i=1,k,x,y;i<=m;i++) {
x=read();y=read();k=read();
int ans=query(1,len,rt[x-1],rt[y],k);
printf("%d\n",b[ans]);
}
return 0;
}
KUR-Couriers
就是个板子题。。。
直接把数组插入主席树,询问时候分三步
\(if(2*(val[ls[y]]-val[ls[x]])>v) ~return ~query(l,mid,ls[x],ls[y],v);\)
\([l,mid]\)里能不能满足
\(if(2*(val[rs[y]]-val[rs[x]])>v) ~return ~query(mid+1,r,rs[x],rs[y],v);\)
\([mid+1,r]\)里能不能满足
都不能就 \(return~ 0;\)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=4e7+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
#define mid ((l+r)>>1)
int n,m,x;
int cnt,ls[N],rs[N],val[N],root[N];
void modify(int l,int r,int pos,int &p){
int x=++cnt;
ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
if(l==r) return;
if(pos<=mid) modify(l,mid,pos,ls[p]);
else modify(mid+1,r,pos,rs[p]);
}
int query(int l,int r,int x,int y,int v){
if(l==r) return l;
if(2*(val[ls[y]]-val[ls[x]])>v) return query(l,mid,ls[x],ls[y],v);
if(2*(val[rs[y]]-val[rs[x]])>v) return query(mid+1,r,rs[x],rs[y],v);
return 0;
}
int main(){
n=read();m=read();
root[0]=0;
for(int i=1;i<=n;i++){
x=read();
root[i]=root[i-1],modify(1,n,x,root[i]);
}
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
printf("%d\n",query(1,n,root[x-1],root[y],y-x+1));
}
return 0;
}
CF840D Destiny
和上题一毛一样吧
就除以2变成除以k
注意不能和上面那个一样给分子乘k——反正会出锅,题目要求严格大于,所以就直接除吧,自带下取整
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
//typedef long long ll;
#define int long long
const int N=10000005;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m,rt[N];
int a[N];
int tree_cnt,ls[N],rs[N],val[N];
#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
if(!p) p=++tree_cnt;
if(l==r) return;
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
}
void modify(int l,int r,int pos,int &p) {
int x=++tree_cnt;
ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
if(l==r) return;
if(pos<=mid) modify(l,mid,pos,ls[p]);
else modify(mid+1,r,pos,rs[p]);
}
int k;
int query(int l,int r,int x,int y,int v) {
if(l==r) return l;
int ans=0;
if(val[ls[y]]-val[ls[x]]>v) {
ans=query(l,mid,ls[x],ls[y],v);
if(ans!=-1) return ans;
}
if(val[rs[y]]-val[rs[x]]>v) {
ans=query(mid+1,r,rs[x],rs[y],v);
if(ans!=-1) return ans;
}
return -1;
}
signed main() {
n=read();m=read();
for(int i=1;i<=n;i++)
rt[i]=rt[i-1],modify(1,n,read(),rt[i]);
for(int i=1;i<=m;i++) {
int l=read(),r=read();k=read();
printf("%lld\n",query(1,n,rt[l-1],rt[r],(r-l+1)/k));
}
return 0;
}
Count on a tree
很明显主席树有个差分的思想,刚才序列上的问题,变成了树上,其实很简单,树上差分,就\(u,v++, lca,fa[lca]--\) ,然后树剖求个\(lca\)就好,注意这里的\(modify\)和上面的都不同,因为是树上问题,所以不能简单继承前一个,而是要继承\(fa\)
对\(dfn\)序值域,求权值第\(k\)小
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=101000;
const int M=4001000;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int hd[N],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
int n,m;
int son[N],dep[N],fa[N],siz[N];
void dfs_son(int x,int f) {
fa[x]=f;siz[x]=1;dep[x]=dep[f]+1;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==f) continue;
dfs_son(y,x);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
int top[N];
void dfs_chain(int x,int tp) {
top[x]=tp;
if(son[x]) dfs_chain(son[x],tp);
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==fa[x]||y==son[x]) continue;
dfs_chain(y,y);
}
}
int LCA(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
#define mid ((l+r)>>1)
int ls[M],rs[M],rt[N],val[M],cnt;
void modify(int l,int r,int pos,int p,int &x) {
x=++cnt;
ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1;
if(l==r) return;
if(pos<=mid) modify(l,mid,pos,ls[p],ls[x]);
else modify(mid+1,r,pos,rs[p],rs[x]);
}
int a[N],b[N],len;
void build(int x) {
modify(1,len,a[x],rt[fa[x]],rt[x]);
for(int i=hd[x];i;i=nxt[i])
if(to[i]!=fa[x])
build(to[i]);
}
int query(int l,int r,int k,int u,int v,int x,int y) {
if(l==r) return l;
int tmp=val[ls[u]]+val[ls[v]]-val[ls[x]]-val[ls[y]];
if(k<=tmp) return query(l,mid,k,ls[u],ls[v],ls[x],ls[y]);
else return query(mid+1,r,k-tmp,rs[u],rs[v],rs[x],rs[y]);
}
int main() {
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=b[i]=read();
sort(b+1,b+1+n);
len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
for(int i=1;i<n;i++) {
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs_son(1,0);
dfs_chain(1,1);
rt[0]=++cnt;
build(1);
int ans=0;
for(int i=1;i<=m;i++) {
int u=read(),v=read(),k=read();
u^=ans;
int lca=LCA(u,v);
ans=b[query(1,len,k,rt[u],rt[v],rt[lca],rt[fa[lca]])];
printf("%d\n",ans);
}
return 0;
}
P3939 数颜色
假主席树
CF893F Subtree Minimum Query
主席树二维数点
第一维时间戳按深度一层一层往下,在上面建第二维权值,由前缀和思想,对于 每个询问就找dep[x]+k的深度上访问最小值就好
对dep值域求val最小
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=100500;
const int inf=1e9+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m,val[N];
int ls[N<<5],rs[N<<5],mn[N<<5];
int rt[N],L[N],R[N],dfn_cnt,dep[N];
int hd[N],to[N*10],nxt[N*10],tot;
inline void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
vector<int> v[N];
void dfs(int x,int fa) {
L[x]=++dfn_cnt;
dep[x]=dep[fa]+1;
v[dep[x]].push_back(x);
for(int i=hd[x];i;i=nxt[i])
if(to[i]!=fa)
dfs(to[i],x);
R[x]=dfn_cnt;
}
#define mid ((l+r)>>1)
int cnt;
void modify(int l,int r,int pos,int v,int &p) {
int x=++cnt;
ls[x]=ls[p];rs[x]=rs[p],mn[x]=min(mn[p],v),p=x;
if(l==r) return;
if(pos<=mid) modify(l,mid,pos,v,ls[p]);
else modify(mid+1,r,pos,v,rs[p]);
}
int query(int l,int r,int L,int R,int p) {
if(L<=l&&r<=R) return mn[p];
int res=inf;
if(L<=mid) res=min(res,query(l,mid,L,R,ls[p]));
if(R>mid) res=min(res,query(mid+1,r,L,R,rs[p]));
return res;
}
int main() {
n=read();int root=read();
for(int i=1;i<=n;i++) val[i]=read();
int x,y;
for(int i=1;i<n;i++) {
x=read();y=read();
add(x,y);add(y,x);
}
mn[0]=inf;
dfs(root,0);
for(int i=1;i<=n;i++,rt[i]=rt[i-1])
for(auto x:v[i])
modify(1,n,L[x],val[x],rt[i]);
m=read();
int ans=0;
while(m--) {
int x=(read()+ans)%n+1,k=(read()+ans)%n;
printf("%d\n",ans=query(1,n,L[x],R[x],rt[min(dep[x]+k,n)]));
}
return 0;
}
FJOI2016神秘数
首先对于一个集合,假设它能表示的数为\([0,sum]\)
在一个区间上,可以把区间里的数拿出来,从小到大排序为\(a_1,a_2\cdots a_n\),在答案不为\(1\)的情况下明显有\(a_1=1\).
然后从小到大把数加进集合,假设当前集合内数的上界是\(Max\),那么要加入的数一定在\([Max+1,sum+1]\)内才可以。
把这个值域内的所有数全部加入,设这个值域内的数的和为\(tmp\),此时\(Max\)要变成\(sum+1\)(因为之前的数都加过了),而\(sum\)要加上\(tmp\).
如果某个时刻\(tmp=0\),那么答案就是\(sum+1\).
因为这个涉及到区间内求某段权值区间内所有元素的和,所以要用到主席树。
因为是迭代更新\(Max\)和\(sum\),再加上主席树的复杂度,总的复杂度是\(\Theta(mlog_2^2 \sum a_i)\).
注意这里\(query\)函数的写法,必须严格这样写
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=100005;
const int inf=1e9;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,rt[N];
int ls[N<<5],rs[N<<5];
ll val[N<<5];
int cnt;
#define mid ((l+r)>>1)
void modify(int l,int r,int v,int &p) {
int x=++cnt;
ls[x]=ls[p];rs[x]=rs[p];
val[x]=val[p]+v; p=x;
if(l==r) return;
if(v<=mid) modify(l,mid,v,ls[p]);
else modify(mid+1,r,v,rs[p]);
}
ll query(int l,int r,int L,int R,int x,int y) {
if(!(val[y]-val[x])) return 0;
if(l==L&&r==R) return val[y]-val[x];
if(R<=mid) return query(l,mid,L,R,ls[x],ls[y]);
else if(L>mid) return query(mid+1,r,L,R,rs[x],rs[y]);
else return query(l,mid,L,mid,ls[x],ls[y])+query(mid+1,r,mid+1,R,rs[x],rs[y]);
}
int main() {
n=read();
for(int i=1;i<=n;i++,rt[i]=rt[i-1])
modify(1,inf,read(),rt[i]);
int m=read();
while(m--) {
int l=read(),r=read();
ll mx=0,sum=0;
while(1) {
ll tmp=query(1,inf,mx+1,sum+1,rt[l-1],rt[r]);
if(!tmp) break;
mx=sum+1;sum+=tmp;
}
printf("%lld\n",sum+1);
}
return 0;
}
MIDDLE
人生第二道黑题
首先是求中位数,这里有一种思想转换:
二分答案\(ans\),把序列中小于\(ans\)的设为\(-1\),大于等于\(ans\)的设为\(1\)(复杂度O(n)),题目变成了求指定区间内的最大子段和(判断>=0即合理),线段树可以解决
但是不能每次二分一个\(ans\)(何况还有\(q\)个询问)都建一遍树,因此可以考虑对于不同的\(ans\)的值建一颗主席树
先把\(ans=1\)(离散化之后,额我没离散化)的线段树建出来,然后ans增加的时候树上只需要单点修改(套主席树的板子)
\(check\)详解:
\([a,b]\)求一个最大后缀子段和, \([c, d]\)求一个最大前缀子段和, \([b+1, c-1]\)求一个和(若\(c\)在\(b\)左边不求)
注意!!!$q[i]=(q[i]+ans)%n+1; $一定要加\(1\) ,不然会挂
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e6+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct node{
int v,pos;
node(){}
node(int a,int b):v(a),pos(b){}
}a[N];
struct tree{
int sum,lmx,rmx;
tree(){}
tree(int a,int b,int c):sum(a),lmx(b),rmx(c){}
tree operator + (const tree &x)const {
return tree(sum+x.sum,max(lmx,sum+x.lmx),max(x.rmx,rmx+x.sum));
}
}t[N];
int n,m,ans;
int q[10];
int cnt,ls[N],rs[N],val[N],root[N];
#define mid ((l+r)>>1)
void build(int l,int r,int &p){//建空树
p=++cnt;
if(l==r){ t[p]=tree(1,1,1); return; }
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
t[p]=t[ls[p]]+t[rs[p]];
}
void modify(int l,int r,int pos,int &p){
int x=++cnt;
ls[x]=ls[p],rs[x]=rs[p],t[x]=t[p],p=x;
if(l==r) { t[p]=tree(-1,-1,-1); return; }
if(pos<=mid) modify(l,mid,pos,ls[p]);
else modify(mid+1,r,pos,rs[p]);
t[p]=t[ls[p]]+t[rs[p]];
}
tree query(int l,int r,int L,int R,int p){
if(L<=l&&r<=R) return t[p];
if(R<=mid) return query(l,mid,L,R,ls[p]);
else if(L>mid) return query(mid+1,r,L,R,rs[p]);
else return query(l,mid,L,R,ls[p])+query(mid+1,r,L,R,rs[p]);
}
bool check(int x){
int sum=0;
if(q[2]+1<=q[3]-1) sum+=query(1,n,q[2]+1,q[3]-1,root[x]).sum;
sum+=query(1,n,q[1],q[2],root[x]).rmx;
sum+=query(1,n,q[3],q[4],root[x]).lmx;
return sum>=0?1:0;
}
bool cmp(node a,node b){
return a.v<b.v;
}
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=node(read(),i);
sort(a+1,a+1+n,cmp);
build(1,n,root[1]);
for(int i=2;i<=n;i++) root[i]=root[i-1],modify(1,n,a[i-1].pos,root[i]);
m=read();
for(int i=1;i<=m;i++){
for(int i=1;i<=4;i++) q[i]=read();
for(int i=1;i<=4;i++) q[i]=(q[i]+ans)%n+1;
sort(q+1,q+5);
int l=1,r=n,o;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) o=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans=a[o].v);
}
return 0;
}