zoukankan      html  css  js  c++  java
  • 可持久化线段树&主席树

    可持久化线段树

    注意空间开大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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",ans=query(1,n,L[x],R[x],rt[min(dep[x]+k,n)]));
    	}
    	return 0;
    }
    

    FJOI2016神秘数

    首先对于一个集合,假设它能表示的数为([0,sum])

    在一个区间上,可以把区间里的数拿出来,从小到大排序为(a_1,a_2cdots 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
    ",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)左边不求)

    image-20200722104346567

    注意!!!$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
    ",ans=a[o].v);
        }
        return 0;
    }
    

    https://www.cnblogs.com/luoyj/p/13750090.html

  • 相关阅读:
    pickle示例
    Python 升级致yum 问题,pip 异常
    jdk 环境
    zookeeper
    Kafka-Monitor
    Kafka
    nxlog 日志采集
    elasticsearch 基本配置
    elasticsearch 单机多实例
    Elaticsearch 集群
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13499813.html
Copyright © 2011-2022 走看看