线段树

线段树

基础的就不写了

动态开点

其实是很简单

就是将节点重编号,做到用的到再开。

对于权值线段树或值域特大操作较少的线段树非常有用。

就是注意离散化和动态开点还是有区别的:

  • 动态开点是 \(\log \text{值域}\) 的时空复杂度,离散化是 \(\log N\) 的复杂度,常数还是有差距的(一般是卡常问题)。
  • 在线段树合并时,离散化的空间复杂度无法保证,相当于是 \(nm\) 的(\(m\) 是线段树个数)。

所以有的题就既要离散化,又要动态开点,如领导集团问题(如果是打假了请D我,顺便告诉我正确的>(如果是打假了请D我,顺便告诉我正确的

好像很少有专门例题,这里放一个 P3372【模板】线段树1 的动态开点写法(码风略毒

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
const int N=1e5+3;
int n,m;

namespace OI{
	template<typename T>
	inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
    template<typename T,typename... Args>
    inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T>
	inline void write(T x){
        static T st[45];T top=0;
        if(x<0)x=~x+1,putchar('-');
        do{st[top++]=x%10;}while(x/=10);
        while(top)putchar(st[--top]^48);
    }
    inline void write(const char c){putchar(c);}
    inline void write(const char c[]){
        int len=strlen(c);
        for(int i=0;i<len;i++) putchar(c[i]);
    }
    template<typename T,typename... Args>
    inline void write(T x,Args... args){write(x);write(args...);}
}
#define read OI::read
#define write OI::write
class Stree{
    #define mid ((l+r)>>1)
    #define lid lt[id]
    #define rid rt[id]
    private:
        int lt[N<<1],rt[N<<1],tot_=1;
        llt sum[N<<1],lz[N<<1];
    public:
        inline void pushup(int id){
            sum[id]=sum[lid]+sum[rid];
        }
        inline void pushdown(int l,int r,int id){
            if(lz[id]){
                if(lid||!lid){
                    sum[lid]+=(mid-l+1)*lz[id];
                    lz[lid]+=lz[id];
                }
                if(rid||!rid){
                    sum[rid]+=(r-mid)*lz[id];
                    lz[rid]+=lz[id];
                }
                lz[id]=0;
            }
        }
        void add(int L,int R,int l,int r,int t,int& id){
            if(!id) id=++tot_;
            if(L<=l&&r<=R) sum[id]+=t*(r-l+1),lz[id]+=t;
            else{
                pushdown(l,r,id);
                if(L<=mid) add(L,R,l,mid,t,lid);
                if(R>mid) add(L,R,mid+1,r,t,rid);
                pushup(id);
            }
        }
        llt get(int L,int R,int l,int r,int id){
            if(!id) return 0;
            if(L<=l&&r<=R) return sum[id];
            pushdown(l,r,id);
            llt res=0;
            if(L<=mid) res+=get(L,R,l,mid,lid);
            if(mid<R) res+=get(L,R,mid+1,r,rid);
            return res;
        }
    #undef mid
    #undef lid
    #undef rid
}st;


signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
    read(n,m);
    for(int i=1;i<=n;i++){
        int x,id=1;read(x);
        st.add(i,i,1,n,x,id);
    }
    for(int i=1;i<=m;i++){
        int p,x,y,k,id=1;read(p,x,y);
        if(p==1) read(k),st.add(x,y,1,n,k,id);
        else write(st.get(x,y,1,n,1),'\n');
    }
}

权值线段树:

可以说就是将桶数组变成线段树,就可以维护一些其他东西,因为是值域的大小,一般结合动态开点、离散化。

挺好理解,看看例题就差不多了。

例题

  • P3369【模板】普通平衡树

    用权值线段树维护区间和:

    1. 插入/删除 \(x\):

      就在 \(x\) 的位置 \(\pm 1\)

    2. 查询 \(x\) 数的排名:

      统计 \(1\)\(x-1\) 的区间和 \(+1\)

    3. 查询排名为 \(x\) 的数:

      寻找一个位置 \(a\) 使得 \(qsum_{a-1}\le x \le qsum_a\)\(qsum_x\) 表示 \(x\) 位置的前缀和

      这个过程可以通过动态减在递归时实现

      具体的可以看代码

    4. 查询 \(x\) 的前驱:

      这个可以通过 \(2,3\) 实现

      查询以 \(x\) 的排名 \(-1\) 为排名的数

      比较好理解的,可以举几个例子试一下。

    5. 查询 \(x\) 的后继:

      类似于是前驱,也通过 \(2,3\) 实现

      查询以 \(x+1\) 的排名为排名的数

      也比较好理解。

    可以稍微思考一下为什么 \(4\) 是排名减一而 \(5\) 是数加一

    这里给出一组数据:

    点击查看 1 1 1 3 1 3 1 4 5 3 6 3

    有没理解的可以看代码:

    CODE
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long llt;
    const int N=1e5+3,Max=1e7+3;
    struct Stree{int l,r,sum;}ct[N<<2];
    int n,tot_=1;
    namespace OI{
    	template<typename T>
    	inline void read(T &x){
    		char s=getchar();x=0;bool pd=false;
    		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
    		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
    		if(pd) x=-x;
    	}
    	template<typename T,typename... Args>
    	inline void read(T& x,Args&... args){read(x);read(args...);}
    	template<typename T>
    	inline void write(T x){
    		static T st[45];T top=0;
    		if(x<0)x=~x+1,putchar('-');
    		do{st[top++]=x%10;}while(x/=10);
    		while(top)putchar(st[--top]^48);
    	}
    	template<typename T>
    	inline void write(T x,const char c){write(x);putchar(c);}
    	template<typename T,typename... Args>
    	inline void write(T& x,Args&... args){write(x);putchar(' '),write(args...);}
    }
    #define read OI::read
    #define write OI::write
    #define read OI::read
    #define write OI::write
    namespace{
    	#define mid ((l+r)>>1)
    	#define lid ct[id].l
    	#define rid ct[id].r
    	inline void pushup(int id){ct[id].sum=ct[lid].sum+ct[rid].sum;}
    	void add(int l,int r,int x,int t,int& id){
    		if(!id) id=++tot_;
    		if(l==r) ct[id].sum+=t;
    		else{
    			if(x<=mid) add(l,mid,x,t,lid);
    			if(mid<x) add(mid+1,r,x,t,rid);
    			pushup(id);
    		}
    	}
    	int getrank(int l,int r,int x,int id){
    		if(!id) return -1;
    		if(l==r) return 1;
    		if(x<=mid) return getrank(l,mid,x,lid);
    		return ct[lid].sum+getrank(mid+1,r,x,rid);
    	}
    	int getval(int l,int r,int x,int id){
    		if(l==r) return l;
    		if(ct[lid].sum<x) return getval(mid+1,r,x-ct[lid].sum,rid);
    		return getval(l,mid,x,lid);
    	}
    	#undef mid
    	#undef lid
    	#undef rid
    }
    
    signed main(){
    #ifndef ONLINE_JUDGE
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	read(n);
    	for(int i=1;i<=n;i++){
    		int p,x,id=1;
    		read(p,x);
    		if(p==1) add(-Max,Max,x,1,id);
    		else if(p==2) add(-Max,Max,x,-1,id);
    		else if(p==3) write(getrank(-Max,Max,x,id),'\n');
    		else if(p==4) write(getval(-Max,Max,x,id),'\n');
    		else if(p==5) write(getval(-Max,Max,getrank(-Max,Max,x,id)-1,id),'\n');
    		else write(getval(-Max,Max,getrank(-Max,Max,x+1,id),id),'\n');
    	}
    }
    

线段树合并:

就是将几棵线段树两两合并到一起的算法,一般是将值合和标记合并(如区间加),有的也将标记下移到叶子(或未开节点)合并(如this)(但好像几乎不会遇见

总的来说是将线段树的信息合并,但方法因题而异。

例题可以直接看下面,这里放个板子。

CODE
void merge(int& id1,int& id2,int l,int r){
		if(!id1||!id2) id1+=id2;
		else{
			sum[id1]+=sum[id2],tg[id1]+=tg[id2];//合并信息
			merge(lt[id1],lt[id2],l,mid);
			merge(rt[id1],rt[id2],mid+1,r);
		}
	}

例题:

1. 蒟蒻的数列

有点诈骗

因为是最后输出和,可以离线

将所有操作离线后按修改的值升序

然后就变成了区间覆盖,区间求和

可以离散化或动态开点做

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
#define int llt
const int N=4e4+3,M=1e9+1;
int n;
struct Ques{
	int x,y,k;
	bool operator<(Ques a){return this->k<a.k;}
}c[N];
	namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
	template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
		static T st[45];T top=0;
		if(x<0)x=~x+1,putchar('-');
		do{st[top++]=x%10;}while(x/=10);
		while(top)putchar(st[--top]^48);
	}
	inline void write(const char c){putchar(c);}
	inline void write(const char *c){
		int len=strlen(c);
		for(int i=0;i<len;i++) putchar(c[i]);
	}
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
	inline void Write(const char c){write(c);}
	inline void Write(const char *c){write(c);}
	template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
	template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write
class Stree{
private:
	int lt[N*30],rt[N*30],sum[N*30],s[N*30],tot_=1;
public:
	#define mid ((l+r)>>1)
	inline void pushup(int id){sum[id]=sum[lt[id]]+sum[rt[id]];}
	inline void pushdown(int l,int r,int id){
		if(s[id]){
			int ls=s[id];s[id]=0;
			if(!lt[id])lt[id]=++tot_;
			if(!rt[id])rt[id]=++tot_;
			s[lt[id]]=ls;
			s[rt[id]]=ls;
			sum[lt[id]]=(mid-l+1)*ls;
			sum[rt[id]]=(r-mid)*ls;
		}
	}
	void updata(int L,int R,int l,int r,int t,int& id){
		if(!id) id=++tot_;
		if(L<=l&&r<=R) sum[id]=(r-l+1)*t,s[id]=t;
		else{
			pushdown(l,r,id);
			if(L<=mid) updata(L,R,l,mid,t,lt[id]);
			if(mid<R) updata(L,R,mid+1,r,t,rt[id]);
			pushup(id);
		}
	}
	int getsum(int L,int R,int l,int r,int id){
		if(!id) return 0;
		if(L<=l&&r<=R) return sum[id];
		pushdown(l,r,id);
		int res=0;
		if(L<=mid) res+=getsum(L,R,l,mid,lt[id]);
		if(mid<R) res+=getsum(L,R,mid+1,r,rt[id]);
		return res;
	}
	#undef mid
}st;
signed main(){
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
	read(n);
	for(int i=1;i<=n;i++) read(c[i].x,c[i].y,c[i].k);
	sort(c+1,c+1+n);
	for(int id=1,i=1;i<=n;i++) st.updata(c[i].x,c[i].y-1,1,M,c[i].k,id);
	write(st.getsum(1,M,1,M,1));
}

2.Promotion Counting P

对每个节点维护一棵线段树,在第 \(i\) 节点回溯合并后查询 \(\sum_{k=i+1}^N p_k\)

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
const int N=1e5+3,MAX=1e9+1;
int n,cv[N],ans[N];

namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
    template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
        static T st[45];T top=0;
        if(x<0)x=~x+1,putchar('-');
        do{st[top++]=x%10;}while(x/=10);
        while(top)putchar(st[--top]^48);
    }
    inline void write(const char c){putchar(c);}
    inline void write(const char *c){
        int len=strlen(c);
        for(int i=0;i<len;i++) putchar(c[i]);
    }
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
    inline void Write(const char c){write(c);}
    inline void Write(const char *c){write(c);}
    template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
    template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write
class Stree{
private:
    int rt[N*50],lt[N*50],sum[N*50],tot_;
public:
    #define mid ((l+r)>>1)
    int ro[N*50];
    inline void pushup(int id){sum[id]=sum[lt[id]]+sum[rt[id]];}
    void add(int x,int l,int r,int t,int& id){
        if(!id) id=++tot_;
        if(l==r) sum[id]+=t;
        else{
            if(x<=mid) add(x,l,mid,t,lt[id]);
            else add(x,mid+1,r,t,rt[id]);
            pushup(id);
        }
    }
    int getans(int x,int l,int r,int id){
        if(!id) return 0;
        if(l==r) return sum[id];
        if(x<=mid) return getans(x,l,mid,lt[id])+sum[rt[id]];
        else return getans(x,mid+1,r,rt[id]);
    }
    void merge(int& id1,int id2,int l,int r){
        if(!id1||!id2) id1+=id2;
        else if(l==r) sum[id1]+=sum[id2];
        else{
            merge(lt[id1],lt[id2],l,mid);
            merge(rt[id1],rt[id2],mid+1,r);
            pushup(id1);
        }
    }
}st;
class TO{
private:
    int hd[N],nt[N<<1],to[N<<1],tot_;
public:
    #define For_to(i,x) for(int i=hd[x];~i;i=nt[i])
    TO(){memset(hd,-1,sizeof hd);}
    inline void add(int x,int y){
        to[tot_]=y,nt[tot_]=hd[x],hd[x]=tot_++;
    }
    void dfs(int x){
        For_to(i,x){
            int y=to[i];
            dfs(y);
            st.merge(st.ro[x],st.ro[y],1,MAX);
        }
        ans[x]=st.getans(cv[x]+1,1,MAX,st.ro[x]);
    }
    #undef For_to
}to;

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
    read(n);
    for(int i=1;i<=n;i++) read(cv[i]),st.add(cv[i],1,MAX,1,st.ro[i]);
    for(int i=2;i<=n;i++){
        int x;read(x);
        to.add(x,i);
    }
    to.dfs(1);
    for(int i=1;i<=n;i++) write(ans[i],'\n');
}

还可以先dfs序,就变成了求静态区间rank。

具体就是先离散化后将所有序列里的值和询问按值降序,在树状数组(线段树)按序列中的数的原位置依次加一,再加完所有 \(\ge k\) 的数后对值 \(=k\) 的询问查询区间和。知乎上的解法(方法一)

3.雨天的尾巴

树上差分 \(+\) 线段树合并

首先,对于每个节点,开一棵动开线段树,用来存储标记的类型

然后的链修改,就按树上差分对节点加对应类型的标记。

回溯时将线段树合并,标记也就合并了。

CODE


#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
const int N=1e5+3;
int n,m,ans[N];

namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
    template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
        static T st[45];T top=0;
        if(x<0)x=~x+1,putchar('-');
        do{st[top++]=x%10;}while(x/=10);
        while(top)putchar(st[--top]^48);
    }
    inline void write(const char c){putchar(c);}
    inline void write(const char *c){
        int len=strlen(c);
        for(int i=0;i<len;i++) putchar(c[i]);
    }
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
    inline void Write(const char c){write(c);}
    inline void Write(const char *c){write(c);}
    template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
    template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write

class Stree{
private:
    int tot_,lt[N*100],rt[N*100],ma[N*100],sum[N*100];
public:
    int root[N];
    #define mid (l+r>>1)
    inline void pushup(int id){
        sum[id]=sum[lt[id]]+sum[rt[id]];
        ma[id]=max(ma[lt[id]],ma[rt[id]]);
    }
    void add(int x,int l,int r,int t,int& id){
        if(!id) id=++tot_;
        if(l==r) sum[id]+=t,ma[id]=sum[id];
        else{
            if(x<=mid) add(x,l,mid,t,lt[id]);
            else add(x,mid+1,r,t,rt[id]);
            pushup(id);
        }
    }
    int getmax(int l,int r,int id){
        if(!id||ma[id]<=0) return 0;
        if(l==r) return l;
        if(ma[lt[id]]>=ma[rt[id]]) return getmax(l,mid,lt[id]);
        else return getmax(mid+1,r,rt[id]);
    }
    void merge(int& id1,int id2,int l,int r){
        if(!id1||!id2) id1+=id2;
        else if(l==r)
            sum[id1]+=sum[id2],ma[id1]=sum[id1];
        else{
            merge(lt[id1],lt[id2],l,mid);
            merge(rt[id1],rt[id2],mid+1,r);
            pushup(id1);
        }
    }
    #undef mid
}st;
class TO{
private:
    int fa[N][21],dep[N],hd[N],nt[N<<1],to[N<<1],tot_;
public:
    #define For_to(i,x) for(int i=hd[x];~i;i=nt[i])
    TO(){memset(hd,-1,sizeof hd);}
    inline void add(int x,int y){
        to[tot_]=y,nt[tot_]=hd[x],hd[x]=tot_++;
    }
    void init(int x,int d){
        dep[x]=d;
        For_to(i,x){
            int y=to[i];
            if(!dep[y]){
                fa[y][0]=x;
                for(int i=1;i<=20;i++) fa[y][i]=fa[fa[y][i-1]][i-1];
                init(y,d+1);
            }
        }
    }
    int getlca(int x,int y){
        if(dep[x]<dep[y]) swap(x,y);
        for(int i=20;i>=0;i--) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
        if(x==y) return x;
        for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        return fa[x][0];
    }
    int getf(int x){return fa[x][0];}
    void dfs(int x){
        For_to(i,x){
            int y=to[i];
            if(y!=getf(x)){
                dfs(y);
                st.merge(st.root[x],st.root[y],1,N);
            }
        }
        ans[x]=st.getmax(1,N,st.root[x]);
    }
    #undef For_to
}to;

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
    read(n,m);
    for(int i=1;i<n;i++){
        int x,y;read(x,y);
        to.add(x,y),to.add(y,x);
    }
    to.init(1,1);
    for(int i=1;i<=m;i++){
        int x,y,z;read(x,y,z);
        int w=to.getlca(x,y);
        st.add(z,1,N,1,st.root[x]);
        st.add(z,1,N,1,st.root[y]);
        st.add(z,1,N,-1,st.root[w]);
        st.add(z,1,N,-1,st.root[to.getf(w)]);
    }
    to.dfs(1);
    for(int i=1;i<=n;i++) write(ans[i],'\n');
}

4. 魔法少女LJJ

有点诈骗。

稍微观察一下数据范围,可以发现 \(c \le 7\),所以不会有 \(8,9\) 操作。

唯一难点就是维护 \(6\) 积的大小,由于数据范围,不能直接乘。

考虑 \(\log(n)\log(m)=\log(nm)\)\(n<m \Leftrightarrow \log(n)<\log(m)\)

所以可以直接维护 \(\log\) 的区间积。

CODE


#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
const int N=4e5+3,MAX=1e9+1;
int n,tot_;

namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
    template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
        static T st[45];T top=0;
        if(x<0)x=~x+1,putchar('-');
        do{st[top++]=x%10;}while(x/=10);
        while(top)putchar(st[--top]^48);
    }
    inline void write(const char c){putchar(c);}
    inline void write(const char *c){
        int len=strlen(c);
        for(int i=0;i<len;i++) putchar(c[i]);
    }
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
    inline void Write(const char c){write(c);}
    inline void Write(const char *c){write(c);}
    template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
    template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write
class Stree{
private:
    int rt[N*20],lt[N*20],sum[N*20],tot_=1;
    bool cl[N*20];
public:
    #define mid ((l+r)>>1)
    int ro[N*20];
    double lg[N*20];
    inline void pushup(int id){
        sum[id]=sum[lt[id]]+sum[rt[id]];
        lg[id]=lg[lt[id]]+lg[rt[id]];
    }
    inline void pushdown(int l,int r,int id){
        if(cl[id]){
            cl[id]=0;
            if(lt[id]){
                cl[lt[id]]=1;
                sum[lt[id]]=0;
                lg[lt[id]]=0;
            }
            if(rt[id]){
                cl[rt[id]]=1;
                sum[rt[id]]=0;
                lg[rt[id]]=0;
            }
        }
    }
    int getsum(int L,int R,int l,int r,int id){
        if(!id) return 0;
        if(L<=l&&r<=R) return sum[id];
        pushdown(l,r,id);
        int res=0;
        if(L<=mid) res+=getsum(L,R,l,mid,lt[id]);
        if(R>mid) res+=getsum(L,R,mid+1,r,rt[id]);
        return res;
    }
    void add(int x,int l,int r,int t,int& id){
        if(!id) id=++tot_;
        if(l==r) sum[id]+=t,lg[id]+=log10(l)*t;
        else{
            pushdown(l,r,id);
            if(x<=mid) add(x,l,mid,t,lt[id]);
            else add(x,mid+1,r,t,rt[id]);
            pushup(id);
        }
    }
    void clr(int L,int R,int l,int r,int id){
        if(!id) return ;
        if(L<=l&&r<=R) sum[id]=0,lg[id]=0,cl[id]=1;
        else{
            pushdown(l,r,id);
            if(L<=mid) clr(L,R,l,mid,lt[id]);
            if(R>mid) clr(L,R,mid+1,r,rt[id]);
            pushup(id);
        }
    }
    int getval(int x,int l,int r,int id){
        if(!id) return 0;
        if(l==r) return l;
        pushdown(l,r,id);
        if(x<=sum[lt[id]]) return getval(x,l,mid,lt[id]);
        else return getval(x-sum[lt[id]],mid+1,r,rt[id]);
    } 
    void merge(int& id1,int id2,int l,int r){
        if(!id1||!id2) id1+=id2;
        else if(l==r) sum[id1]+=sum[id2],lg[id1]=sum[id1]*log10(l);
        else{
            pushdown(l,r,id1);
            pushdown(l,r,id2);
            merge(lt[id1],lt[id2],l,mid);
            merge(rt[id1],rt[id2],mid+1,r);
            pushup(id1);
        }
    }
}st;
class UFond{
private:
    int fa[N];
public:
    UFond(){for(int i=1;i<N;i++)fa[i]=i;}
    int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);}
    void uni(int x,int y){
        int fx=getf(x),fy=getf(y);
        if(fx==fy) return ;
        fa[fy]=fx;
        st.merge(st.ro[fx],st.ro[fy],1,MAX);
    }
}uf;

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
    read(n);
    for(int i=1;i<=n;i++){
        int p,x,y;read(p,x);
        if(p==1){
            st.add(x,1,MAX,1,st.ro[++tot_]);
        }else if(p==2){
            read(y);
            uf.uni(x,y);
        }else if(p==3){
            read(y);
            if(y==1) continue;
            int sum=st.getsum(1,y-1,1,MAX,st.ro[uf.getf(x)]);
            if(sum==0) continue;
            st.clr(1,y-1,1,MAX,st.ro[uf.getf(x)]);
            st.add(y,1,MAX,sum,st.ro[uf.getf(x)]);
        }else if(p==4){
            read(y);
            int sum=st.getsum(y+1,MAX,1,MAX,st.ro[uf.getf(x)]);
            if(sum==0) continue;
            st.clr(y+1,MAX,1,MAX,st.ro[uf.getf(x)]);
            st.add(y,1,MAX,sum,st.ro[uf.getf(x)]);
        }else if(p==5){
            read(y);
            write(st.getval(y,1,MAX,st.ro[uf.getf(x)]),'\n');
        }else if(p==6){
            read(y);
            double a=st.lg[st.ro[uf.getf(x)]],b=st.lg[st.ro[uf.getf(y)]];
            write(a>b?1:0,'\n');
        }else{
            write(st.getsum(1,MAX,1,MAX,st.ro[uf.getf(x)]),'\n');
        }
    }
    return 0;
}

5.领导集团问题

线段树优化 dp。

定义 \(dp_{i,j}\) 表示节点到 \(i\) ,最小值 \(\ge j\) 时可以取到的最大数量。

易得(v 是 i 的子节点

\[dp_{i,j}=\max\{\max_{k_1=j}^{MAX}\{\sum_v dp_{v,k_1}\},\max_{k_2=w_i}^{MAX}\{\sum_v dp_{v,k_2}\}+1\} \]

因为显然,dp_i 是单调不升的,所以原式可以化简成:

\[dp_{i,j}=\max\{\sum_v dp_{v,j},\sum_v dp_{v,w_i}+1\} \]

然后对每个节点开一棵线段树,表示 \(dp_{i,j}\),相加的操作可以合并,\(\max\) 可以二分 \(+\) 区间推平(因为不升

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
#define For(i,a,b) for(int $L=a,$R=b,$D=($L<=$R)-($R<$L),i=$L;i*$D<=$R*$D;i+=$D)
#define Fcr(i,a,b,c) for(int $L=a,$R=b,$C=c,$D=(0<=$R-$L)-($R-$L<0),i=$L;i*$D<=$R*$D;i+=$D*$C)
const int N=2e5+3;
int w[N],n,MAX=1e9+1;
namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
	template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
		static T st[45];T top=0;
		if(x<0)x=~x+1,putchar('-');
		do{st[top++]=x%10;}while(x/=10);
		while(top)putchar(st[--top]^48);
	}
	inline void write(const char c){putchar(c);}
	inline void write(const char *c){
		int len=strlen(c);
		For(i,0,len-1) putchar(c[i]);
	}
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
	inline void Write(const char c){write(c);if(c!='\n') putchar(' ');}
	inline void Write(const char *c){write(c),putchar(' ');}
	template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
	template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write
class LSNUM{
private:
	int ls[N];
public:
	void lsnum(){
		For(i,1,n) ls[i]=w[i];
		sort(ls+1,ls+n+1);
		MAX=unique(ls+1,ls+n+1)-ls;
		For(i,1,n) w[i]=lower_bound(ls+1,ls+MAX,w[i])-ls;
	}
}LSnum;
class ST{
private:
	int lt[N*40],rt[N*40],lz[N*40],tot_;
public:
	#define mid ((l+r)>>1)
	int ro[N*40];
	void add(int L,int R,int l,int r,int x,int &id){
		if(!id) id=++tot_;
		// Write(L,R,l,r,x,'\n');
		if(L<=l&&r<=R) lz[id]+=x;
		else{
			if(L<=mid) add(L,R,l,mid,x,lt[id]);
			if(mid<R) add(L,R,mid+1,r,x,rt[id]);
		}
	}
	int get(int t,int l,int r,int id){
		if(!id) return 0;
		else if(l==r) return lz[id];
		else if(t<=mid) return get(t,l,mid,lt[id])+lz[id];
		else return get(t,mid+1,r,rt[id])+lz[id];
	}
	void merge(int &id1,int id2,int l,int r){
		if(!id1||!id2) id1+=id2;
		else{
			lz[id1]+=lz[id2];
			merge(lt[id1],lt[id2],l,mid);
			merge(rt[id1],rt[id2],mid+1,r);
		}
	}
	#undef mid
}st;
class TO{
private:
	int hd[N],nt[N<<1],to[N<<1],tot_;
public:
	#define For_to(i,x) for(int i=hd[x];~i;i=nt[i])
	TO(){memset(hd,-1,sizeof hd);}
	inline void add(int x,int y){
		to[tot_]=y,nt[tot_]=hd[x],hd[x]=tot_++;
	}
	void dfs(int x){
		For_to(i,x){
			int y=to[i];
			dfs(y);
			st.merge(st.ro[x],st.ro[y],1,MAX);
		}
		int chk=st.get(w[x],1,MAX,st.ro[x])+1;
		int l=1,r=w[x];
		while(l<=r){
			int mid=(l+r)>>1;
			if(st.get(mid,1,MAX,st.ro[x])>=chk) l=mid+1;
			else r=mid-1;
		}
		st.add(l,w[x],1,MAX,1,st.ro[x]);
	}
	#undef For_to
}to;

int main(){
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
	read(n);
	For(i,1,n) read(w[i]);
	LSnum.lsnum();
	for(int i=2;i<=n;i++){
		 int f;read(f);
		 to.add(f,i);
	}
	to.dfs(1);
	Write(max(st.get(w[1],1,MAX,st.ro[1]),st.get(1,1,MAX,st.ro[1])));
}

6.大融合

首先,一个显然的性质,负载 \(=\) 左边的点数 \(\times\) 右边的点数

考虑离线

先求出dfs序,对于每个节点建一棵线段树,在其dfs序的位置 \(+1\)

加边时,将两棵合并,用并查集维护。

查询时,求这条边两个深度较深的一个的子树大小,具体的,在联通快的线段树中查询dfs序中子树的区间的区间和,然后用联通快大小(\(1\)\(N\) 的区间和) \(-\) 子树大小,就可以求出另一边。

最后相乘即可。

CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long llt;
#define int llt
const int N=1e5+2,MAX=1e5+3;
#define For(i,a,b) for(int $L=a,$R=b,$D=($L<=$R)-($R<$L),i=$L;i*$D<=$R*$D;i+=$D)
#define Fcr(i,a,b,c) for(int $L=a,$R=b,$C=c,$D=(0<=$R-$L)-($R-$L<0),i=$L;i*$D<=$R*$D;i+=$D*$C)
struct question{
	char p;int x,y;
}cq[N];
int n,q,din[N],dout[N],dtot_;

namespace IO{
	template<typename T> inline void read(T &x){
		char s=getchar();x=0;bool pd=false;
		while(s<'0'||'9'<s){if(s=='-') pd=true;s=getchar();}
		while('0'<=s&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
		if(pd) x=-x;
	}
	template<typename T,typename... Args> inline void read(T& x,Args&... args){read(x);read(args...);}
	template<typename T> inline void write(T x){
		static T st[45];T top=0;
		if(x<0)x=~x+1,putchar('-');
		do{st[top++]=x%10;}while(x/=10);
		while(top)putchar(st[--top]^48);
	}
	inline void write(const char c){putchar(c);}
	inline void write(const char *c){
		int len=strlen(c);
		For(i,0,len-1) putchar(c[i]);
	}
	template<typename T> inline void Write(T x){write(x);putchar(' ');}
	inline void Write(const char c){write(c);if(c!='\n') putchar(' ');}
	inline void Write(const char *c){write(c),putchar(' ');}
	template<typename T,typename... Args> inline void write(T x,Args... args){write(x);write(args...);}
	template<typename T,typename... Args> inline void Write(T x,Args... args){Write(x);Write(args...);}
}
#define read IO::read
#define write IO::write
#define Write IO::Write
class ST{
private:
	int lt[N*20],rt[N*20],sum[N*20],tg[N*20],tot_;
public:
	int ro[N*20];
	#define mid ((l+r)>>1)
	#define Stg S+tg[id]
	void add(int x,int l,int r,int t,int& id){
		if(!id) id=++tot_;
		sum[id]+=t;
		if(l==r) tg[id]+=t;
		else if(x<=mid) add(x,l,mid,t,lt[id]);
		else add(x,mid+1,r,t,rt[id]);
	}
	int get(int L,int R,int l,int r,int S,int id){
		if(!id) return 0;
		else if(L==l&&R==r) return S*(R-L+1)+sum[id];
		else if(R<=mid) return get(L,R,l,mid,Stg,lt[id]);
		else if(L>mid) return get(L,R,mid+1,r,Stg,rt[id]);
		else return get(L,mid,l,mid,Stg,lt[id])+get(mid+1,R,mid+1,r,Stg,rt[id]);
	}
	void merge(int& id1,int& id2,int l,int r){
		if(!id1||!id2) id1+=id2;
		else{
			sum[id1]+=sum[id2],tg[id1]+=tg[id2];
			merge(lt[id1],lt[id2],l,mid);
			merge(rt[id1],rt[id2],mid+1,r);
		}
	}
	#undef mid
	#undef Stg
}st;
class TO{
private:
	int hd[N],nt[N<<1],to[N<<1],tot_;
public:
	#define For_to(i,x,y) for(int i=hd[x],y=to[i];~i;i=nt[i],y=to[i])
	TO(){memset(hd,-1,sizeof hd);}
	inline void add(int x,int y){
		to[tot_]=y,nt[tot_]=hd[x],hd[x]=tot_++;
	}
	void dfs(int x){
		din[x]=++dtot_;
		For_to(i,x,y)
			if(!din[y]) dfs(y);
        dout[x]=dtot_;
	}
	#undef For_to
}to;
class UFOND{
private:
	int fa[N];
public:
	UFOND(){For(i,1,N)fa[i]=i;}
	int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
	void uni(int x,int y){
		int fx=get(x),fy=get(y);
		if(fx!=fy){
			fa[fy]=fx;
			st.merge(st.ro[fx],st.ro[fy],1,MAX);
		}
	}
}uf;

signed main(){
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
	read(n,q);
	For(i,1,q){
		scanf(" %c",&cq[i].p);
		read(cq[i].x,cq[i].y);
		if(cq[i].p=='A'){
			to.add(cq[i].x,cq[i].y);
			to.add(cq[i].y,cq[i].x);
		}
	}
	For(i,1,n) if(!din[i]) to.dfs(i);
	For(i,1,n) st.add(din[i],1,MAX,1,st.ro[i]);
    For(i,1,q){
		int x=cq[i].x,y=cq[i].y;
		if(cq[i].p=='A') uf.uni(x,y);
		else{
			int fa=uf.get(x);
			int la=st.get(1,MAX,1,MAX,0,st.ro[uf.get(fa)]);
			int lb=min(st.get(din[x],dout[x],1,MAX,0,st.ro[uf.get(x)]),st.get(din[y],dout[y],1,MAX,0,st.ro[uf.get(y)]));
			Write((la-lb)*lb,'\n');
		}
	}

	// For(i,1,n) Write(din[i],dout[i],'\n');

	// read(n);
	// For(i,1,n){
	// 	int p,x,y,id;read(p,x,y);
	// 	if(p==1) read(id),st.add(x,1,MAX,y,st.ro[id]);
	// 	else if(p==2) read(id),write(st.get(x,y,1,MAX,0,st.ro[id]),'\n');
	// 	else st.merge(st.ro[x],st.ro[y],1,MAX);
	// }
}
posted @ 2023-07-30 21:45  xrlong  阅读(7)  评论(0编辑  收藏  举报