一般来说,可以解决的问题是:子树内信息的修改,子树内信息的查询、路径的修改、路径的查询

先是剖分成链,形成序列,然后用处理序列的数据结构处理,也就是线段树

https://www.cnblogs.com/IltzInstallBI/p/12744038.html

我们所常说的树链剖分其实是轻重链剖分
树链剖分可以处理树上的任意两点间路径和任意一点子树的信息修改与查询(配合线段树这样的数据结构..)

建议先学会线段树和 LCA。

首先注意下文中权值是赋在点上的 而不是在边上
如果遇到权值在边上的情况 把权值赋给这条边连接的两点中深度较大的那个点即可

1.引入

线段树是通过维护一些区间 并且把待处理区间拆分成一定数量个维护的区间
树链剖分思想相似 将树剖分成nlog⁡n级别条互相不相交的链 同时保证每一个点都在且仅在一条链上(所有链可以覆盖所有点) 对于每一条路径可以将其拆分成nlog⁡n级别条链,分别维护链上点权
链就是一些点 这些点首尾相接 除了两端的点只有一个点与之相连 其他的点都有两点与之有边相连
在这里的链中 链维护一些深度递增的点 没有任意两点在树中深度相同

2.思想

如果将一条很长的链一起处理 可以优化效率
为了使得链与链不相交 必定有一些边不能在链中 我们把这些边成为轻边 因为这样的边不会很多 遇到时直接处理就行了
于是我们通过一定形式将一棵树分成轻边和链 所有的链都一起处理 而对于轻边则直接一条一条边处理

3.实现

1.预处理

我们把链称为重链
首先我们要让重链的取法最优
我们对于每一个点 显然它到它儿子(我们先随便指定一个点当做根)的所有出边中 只有一条边能在重链中 否则链会相交
在这里我们取子树大小最大的儿子作为重儿子 对于任意一个点该点和它的重儿子位于同一条重链上 到其余的儿子的路径作为轻边
这样就会形成一些重链了 如果觉得太抽象可以看看下面的图片

至于为什么取子树大小最大在后面复杂度证明中会提到

首先我们要通过dfs预处理出每一个点的子树大小、父亲节点编号、深度、重儿子编号。
这些都不难处理 在下面的代码中给出注释了

这时候这棵树已经剖分好了 但是为了维护链上信息 我们还要再处理一些信息
首先每一条链可以视为一个区间 我们可以用线段树来维护链信息

因此 我们需要给树上每一个点编号 代表它在线段树中的编号 因为每一条重链在线段树上必须对应一个连续的区间 而原来的顺序未必能满足这个要求
其次再建立一个数组维护线段树上编号为i的点对应原树上的点的编号 在线段树建树时会用到
所以我们要再dfs一遍 而且这个dfs的遍历顺序要稍微调整一下 通过每一次先遍历点的重儿子再遍历其他儿子来保证每一条重链在线段树上必须对应一个连续的区间

等等 还没结束
现在我们是不知道每一条重链对应的区间编号
所以对于每一个点我们还要处理出它所在重链的顶端的点(该链上深度最小的点)编号

如果一个点在重链的中间 那么只用处理这条重链的一部分就行了(该点到顶端的部分) 链上的点对应线段树中编号是随深度递增的
如果路径上的两个点都在同一条重链上 只要处理这两个点之间的部分就行了

 

如果是求简单的LCA的话,就不需要线段树这样的结构了,因为不涉及到修改,而且也没有rec,rid这样的记录编号的数组。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//树链剖分法求LCA
int n,m,cnt,son[maxn],size[maxn],top[maxn],fa[maxn],dep[maxn];
int head[maxn],to[maxn*2],nex[maxn*2];
void adde(int u,int v){
    to[++cnt]=v;
    nex[cnt]=head[u];
    head[u]=cnt;
}
void dfs1(int x,int f,int deep){
    fa[x]=f;
    size[x]=1;
    dep[x]=deep;
    for(int i=head[x];i;i=nex[i]){
        int t=to[i];
        if(t==f) continue;
        dfs1(t,x,deep+1);
        size[x]+=size[t];
        if(!son[x]||size[t]>size[son[x]]) son[x]=t;
    }
}
int dfn;
void dfs2(int x,int topf){
    top[x]=topf;
    if(!son[x]) return;
    dfs2(son[x],topf);
    for(int i=head[x];i;i=nex[i]){
        int t=to[i];
        if(t==fa[x]||t==son[x]) continue;
        dfs2(t,t);
    }
}
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]?y:x;
}
int dist(int x,int y){
    return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int main(){
    int x,y;
    cin>>n;
    for(int i=1;i<n;i++){
        cin>>x>>y;
        adde(x,y);
        adde(y,x);
    }
    dfs1(1,0,1);
    dfs2(1,1);
    cin>>m;
    while(m--){
        cin>>x>>y;
        printf("%d\n",dist(x,y));
    }
return 0;
}

  

P3384 【模板】轻重链剖分

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#define Rint register int
#define mem(a,b) memset(a,(b),sizeof(a))
#define Temp template<typename T>
using namespace std;
typedef long long LL;
Temp inline void read(T &x){
    x=0;T w=1,ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
    x=x*w;
}

#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)

const int maxn=200000+10;
int n,m,r,mod;
//见题意 
int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];
//链式前向星数组,w[]、wt[]初始点权数组 
int a[maxn<<2],laz[maxn<<2];
//线段树数组、lazy操作 
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
//son[]重儿子编号,id[]新编号,fa[]父亲节点,cnt dfs_clock/dfs序,dep[]深度,siz[]子树大小,top[]当前链顶端节点 
int res=0;
//查询答案 

inline void add(int x,int y){//链式前向星加边 
    to[++e]=y;
    nex[e]=beg[x];
    beg[x]=e;
}
//-------------------------------------- 以下为线段树 
inline void pushdown(int rt,int lenn){
	laz[rt<<1]+=laz[rt];
	laz[rt<<1|1]+=laz[rt];
    a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
    a[rt<<1|1]+=laz[rt]*(lenn>>1);
    a[rt<<1]%=mod;
    a[rt<<1|1]%=mod;
    laz[rt]=0;
}

inline void build(int rt,int l,int r){
    if(l==r){
        a[rt]=wt[l];
        if(a[rt]>mod)a[rt]%=mod;
        return;
    }
    build(lson);
    build(rson);
    a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}

inline void query(int rt,int l,int r,int L,int R){
	if(L<=l&&r<=R){res+=a[rt];res%=mod;return;}
	else{
		if(laz[rt])pushdown(rt,len);
		if(L<=mid)query(lson,L,R);
		if(R>mid)query(rson,L,R);
	}
}

inline void update(int rt,int l,int r,int L,int R,int k){
    if(L<=l&&r<=R){
		laz[rt]+=k;
		a[rt]+=k*len;
	}
	else{
		if(laz[rt])pushdown(rt,len);
		if(L<=mid)update(lson,L,R,k);
		if(R>mid)update(rson,L,R,k);
		a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
	}
}
//---------------------------------以上为线段树 
inline int qRange(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){//当两个点不在同一条链上 
		if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
		ans+=res;
		ans%=mod;//按题意取模 
		x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
	}
	//直到两个点处于一条链上
	if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点
	res=0;
	query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
	ans+=res;
	return ans%mod;
}

inline void updRange(int x,int y,int k){//同上 
	k%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}

inline int qSon(int x){
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 
	return res;
}

inline void updSon(int x,int k){//同上 
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度 
	dep[x]=deep;//标记每个点的深度 
	fa[x]=f;//标记每个点的父亲 
	siz[x]=1;//标记每个非叶子节点的子树大小 
	int maxson=-1;//记录重儿子的儿子数 
	for(Rint i=beg[x];i;i=nex[i]){
		int y=to[i];
		if(y==f)continue;//若为父亲则continue 
		dfs1(y,x,deep+1);//dfs其儿子 
		siz[x]+=siz[y];//把它的儿子数加到它身上 
		if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 
	}
}

inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 
	id[x]=++cnt;//标记每个点的新编号 
	wt[cnt]=w[x];//把每个点的初始值赋到新编号上来 
	top[x]=topf;//这个点所在链的顶端 
	if(!son[x])return;//如果没有儿子则返回 
	dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 
	for(Rint i=beg[x];i;i=nex[i]){
		int y=to[i];
		if(y==fa[x]||y==son[x])continue;
		dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
	}
}

int main(){
    read(n);read(m);read(r);read(mod);
    for(Rint i=1;i<=n;i++)read(w[i]);
    for(Rint i=1;i<n;i++){
        int a,b;
        read(a);read(b);
        add(a,b);add(b,a);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
	while(m--){
        int k,x,y,z;
        read(k);
        if(k==1){
            read(x);read(y);read(z);
            updRange(x,y,z);
        }
        else if(k==2){
            read(x);read(y);
            printf("%d\n",qRange(x,y));
        }
        else if(k==3){
            read(x);read(y);
            updSon(x,y);
        }
        else{
            read(x);
            printf("%d\n",qSon(x));
    	}
    }
}

 

【一本通的题目】

1560:【例 1】树的统计

 给出了每个节点的权值

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=3e4+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
struct node{
	int l,r,summ,maxx;
}a[maxn*4];
int head[maxn];
struct edge{
	int to,nex;
}ed[maxn*2];
int cnt,n,p,tot;
int son[maxn],id[maxn],pre[maxn],fa[maxn],top[maxn],dep[maxn],size[maxn],w[maxn];
void adde(int u,int v){
	ed[++cnt].nex=head[u];
	ed[cnt].to=v;
	head[u]=cnt;
}
void dfs1(int x,int f,int deep){
	size[x]=1;
	dep[x]=deep;
	fa[x]=f;
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].to;
		if(t==f) continue;
		dfs1(t,x,deep+1);
		size[x]+=size[t];
		if(!son[x]||size[t]>size[son[x]]) son[x]=t;
	}
}
void dfs2(int x,int topf){
	top[x]=topf;
	id[x]=++tot;  //节点i在线段树中的编号
	pre[tot]=x;   //线段树中点为i的对应的节点编号
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].to;
		if(t!=son[x]&&t!=fa[x]) dfs2(t,t);
	}
} 
void build(int root,int l,int r){
	a[root].l=l;
	a[root].r=r;
	if(l==r) {
		a[root].maxx=w[pre[l]];
		a[root].summ=w[pre[l]];
		return;
	}
	int mid=(l+r)/2;
	build(root*2,l,mid);
	build(root*2+1,mid+1,r);
	a[root].summ=a[root<<1].summ+a[(root<<1)+1].summ;
	a[root].maxx=max(a[root<<1].maxx,a[(root<<1)+1].maxx);
}
void update(int k,int x,int jia){
	if(a[k].l==a[k].r){
		a[k].summ+=jia*(a[k].r-a[k].l+1);
		a[k].maxx+=jia;
		return;
	}
	int mid=(a[k].l+a[k].r)/2;
	if(x<=mid) update(k<<1,x,jia);
	else update((k<<1)+1,x,jia);
	a[k].summ=a[k<<1].summ+a[(k<<1)+1].summ;
	a[k].maxx=max(a[k<<1].maxx,a[(k<<1)+1].maxx); 
}

int ask_sum(int k,int x,int y){
	if(a[k].l>=x&&a[k].r<=y) return a[k].summ;
	int mid=(a[k].l+a[k].r)/2;
	int res=0;
	if(x<=mid) res+=ask_sum(k<<1,x,y);
	if(y>mid) res+=ask_sum((k<<1)+1,x,y);
	a[k].summ=a[k<<1].summ+a[(k<<1)+1].summ;   //在这里也进行更新 
	a[k].maxx=max(a[k<<1].maxx,a[(k<<1)+1].maxx);
	return res; 
}

int ask_max(int k,int x,int y){
	if(a[k].l>=x&&a[k].r<=y) return a[k].maxx;
	int mid=(a[k].l+a[k].r)/2;
	int res=-INF;
	if(x<=mid) res=ask_max(k<<1,x,y);
	if(y>mid) res=max(res,ask_max((k<<1)+1,x,y));
	a[k].summ=a[k<<1].summ+a[(k<<1)+1].summ;   //在这里也进行更新 
	a[k].maxx=max(a[k<<1].maxx,a[(k<<1)+1].maxx);
	return res; 
}


int get_max(int x,int y){
	int res=-INF;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=max(res,ask_max(1,id[top[x]],id[x]));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res=max(res,ask_max(1,id[x],id[y]));
	return res;
}
int get_sum(int x,int y){
	int res=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res+=ask_sum(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res+=ask_sum(1,id[x],id[y]);
	return res;
}


int main(){
	scanf("%d",&n);
	int a,b;
	for(int i=1;i<n;i++){
		scanf("%d %d",&a,&b);
		adde(a,b);
		adde(b,a);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&w[i]);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	build(1,1,n);
	char op[10];
	scanf("%d",&p);
	while(p--){
		scanf("%s %d %d",op,&a,&b);
		if(op[0]=='C') {
			update(1,id[a],b-w[a]);
			w[a]=b;
		}
		else if(op[1]=='M') printf("%d\n",get_max(a,b));
		else printf("%d\n",get_sum(a,b));
	}
return 0;
}

  

1561:「HAOI2015」树上操作

 点修改、子树内修改、路径求和---------给了每个节点的权值

#include<bits/stdc++.h>
using namespace std;
struct SYM{
    int to,next;
}edge[200010];
struct ASJ{
    long long  sum;
    long long lz;
}tree[400010];
int head[100010],tot;
int n,m;
int w[100010],dep[100010],fa[100010],son[100010],siz[100010],top[100010],wet[100010],id[100010];
void addedge(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void build(int i,int l,int r){                          //建树 
    if(l==r){
        tree[i].sum=wet[l]; ///这个是在dfs2里面处理好了,因为调用顺序是dfs1、dfs2、build 
        return ;
    }
    int mid=(l+r)/2;
    build(2*i,l,mid);                                   //左儿子 
    build(2*i+1,mid+1,r);                               //右儿子 
    tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum);
}
void pushdown(int i,long long len){                    //LAZY下传 ---在破环这个区间的时候使用 
    tree[2*i].lz+=tree[i].lz;
    tree[2*i+1].lz+=tree[i].lz;
    tree[2*i].sum+=(tree[i].lz*(len-len/2));
    tree[2*i+1].sum+=(tree[i].lz*(len/2));
    tree[i].lz=0;                                      //别忘了清零 
}
void update(int i,int l,int r,int L,int R,long long k){//更新操作 
    if(l>=L&&r<=R){
        tree[i].sum+=k*(r-l+1);       
        tree[i].lz+=k;
        return ;
    }
    int mid=(l+r)/2;
    pushdown(i,(r-l+1));                               //下传LAZY 
    if(L<=mid) update(2*i,l,mid,L,R,k);
    if(R>mid) update(2*i+1,mid+1,r,L,R,k);
    tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
}
long long query(int i,int l,int r,int L,int R){//查询操作 
    long long ans=0;
    if(l>=L&&r<=R){
        return tree[i].sum;
    }
    int mid=(l+r)/2;
    pushdown(i,(r-l+1));
    if(L<=mid) ans+=query(2*i,l,mid,L,R);
    if(R>=mid+1) ans+=query(2*i+1,mid+1,r,L,R);
    return ans;
}
//----------------------------------------------------------------上面是线段树 
void dfs1(int now,int from){               //处理dep,fa,siz,以及重儿子son 
    dep[now]=dep[from]+1;
    fa[now]=from;
    int maxson=-1;
    siz[now]=1;
    for(int i=head[now];i;i=edge[i].next){
        int v=edge[i].to;
        if(v==from) continue;
        dfs1(v,now);
        siz[now]+=siz[v];
        if(siz[v]>maxson){
            son[now]=v;
            maxson=siz[v];
        }
    }
}
int cnt;
void dfs2(int now,int topr){              //处理重链链顶top,新点id,新点权值wet 
    id[now]=++cnt;
    top[now]=topr;
    wet[cnt]=w[now];
    if(!son[now]) return;
    dfs2(son[now],topr);                  //先处理重儿子,再处理轻儿子 
    for(int i=head[now];i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa[now]||v==son[now]) continue;
        dfs2(v,v);                        //每个轻儿子都是一个新的链顶,别忘了换链顶!!! 
    }
}
void update1(int x,int k){
    update(1,1,n,id[x],id[x]+siz[x]-1,k); //子树是连续的所以左节点id[x],右节点id[x]+siz[x]-1 
}
long long q1(int x,int y){               //这里我写的有点麻烦,因为一个点固定为根1,所以其实可以省略一些,不过这里的代码是可以应用于每一个树链剖分路经查询的 
    long long ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ans+=query(1,1,n,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    ans+=query(1,1,n,id[x],id[y]);
    return ans;
}
int main(){
    int no,x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    while(m--){
        scanf("%d",&no);
        if(no==1){
            scanf("%d%d",&x,&y);
            update(1,1,n,id[x],id[x],y);                 //单点修改 
        }
        if(no==2){
            scanf("%d%d",&x,&y);                        //子树修改 
            update1(x,y);
        }
        if(no==3){                                      //路径查询 
            scanf("%d",&x);
            printf("%lld\n",q1(x,1));
        }
    }
}

  

1562:「NOI2015」软件包管理器

//依赖关系
//安装一个软件包---》它依赖的会产生影响
//卸载一个包---》依赖它的会产生影响
/*每次安装软件,就把根节点到x软件路径上的值全部变为1 ---路径修改
同理,每次卸载软件,就把x以及它的子树的值变为0 ----子树修改
故我们可以用区间和的思想,每次操作之前记录一下tree[root].sum的值,更新之后再查询一遍tree[root].sum的值,两者之差的绝对值则为答案。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//依赖关系
//安装一个软件包---》它依赖的会产生影响
//卸载一个包---》依赖它的会产生影响
/*每次安装软件,就把根节点到x软件路径上的值全部变为1  ---路径修改 
同理,每次卸载软件,就把x以及它的子树的值变为0       ----子树修改 
故我们可以用区间和的思想,每次操作之前记录一下tree[root].sum的值,更新之后再查询一遍tree[root].sum的值,两者之差的绝对值则为答案。
*/ 
//这道题应该也能用其他方法做吧。。。
int  head[maxn];
int dep[maxn],fa[maxn],son[maxn],top[maxn],id[maxn],pre[maxn],size[maxn];
char s[maxn];
int n,q,tot;
struct edge{
	int to;
	int nex;
}ed[maxn*2];
struct node{
	int l,r,flag,summ;
}tree[maxn*4];  //4倍空间 
void adde(int u,int v){
	ed[++tot].nex=head[u];
	ed[tot].to=v;
	head[u]=tot;
}
void dfs1(int x,int f,int deep){
	dep[x]=deep;
	fa[x]=f;
	size[x]=1;
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].to;
		if(t==f) continue;
		dfs1(t,x,deep+1);
		size[x]+=size[t];
		if(!son[x]||size[t]>size[son[x]]) son[x]=t;
	}
}
int dfn=0;
void dfs2(int x,int topf){
	top[x]=topf;
	id[x]=++dfn;
	pre[dfn]=x;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].to;
		if(t==fa[x]||t==son[x]) continue;
		dfs2(t,t);
	}
}
void build(int x,int l,int r){
	tree[x].l=l;
	tree[x].r=r;
	tree[x].summ=0;
	tree[x].flag=-1;
	if(l==r) return;
	int mid=(l+r)/2;
	build(x*2,l,mid);build(x*2+1,mid+1,r);
	return;
}
void pushd(int x){
	tree[x<<1].summ=(tree[x<<1].r-tree[x<<1].l+1)*tree[x].flag;  //flag是表示状态,summ表示操作次数 
	tree[x<<1|1].summ=(tree[x<<1|1].r-tree[x<<1|1].l+1)*tree[x].flag;
	tree[x<<1].flag=tree[x<<1|1].flag=tree[x].flag;
	tree[x].flag=-1;  //恢复 
}
int get_sum(int x,int l,int r){
	if(tree[x].r<l||tree[x].l>r) return 0;
	if(l<=tree[x].l&&tree[x].r<=r) return tree[x].summ;
	if(tree[x].flag!=-1) pushd(x);
	return get_sum(x<<1,l,r)+get_sum(x<<1|1,l,r);
}
void update(int x,int l,int r,int val){ //线段树--区间修改 
	if(tree[x].r<l||tree[x].l>r) return;
	if(l<=tree[x].l&&tree[x].r<=r) {
		tree[x].summ=(tree[x].r-tree[x].l+1)*val;
		tree[x].flag=val;
		return;
	}
	if(tree[x].flag!=-1) pushd(x);
	update(x<<1,l,r,val);
	update(x<<1|1,l,r,val);
	tree[x].summ=tree[x<<1].summ+tree[x<<1|1].summ;
	return;
}
void rangeup(int u,int v,int val){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u],val);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	update(1,id[u],id[v],val);
	return;
}

int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x;
		scanf("%d",&x);
		adde(++x,i); //有向边 
	}
	dfs1(1,1,1);
	dfs2(1,1);
	build(1,1,n); 
	scanf("%d",&q);
	while(q--){
		int x;
		scanf("%s %d",s,&x);
		x++;
		int t1=tree[1].summ;
		if(s[0]=='i') { //安装---对应的是1--x的这条路径修改 
			rangeup(1,x,1); //安装	
			int t2=tree[1].summ;
			printf("%d\n",abs(t1-t2));
		}
		if(s[0]=='u'){
			update(1,id[x],id[x]+size[x]-1,0); //卸载,是对叶子节点也就只是子树产生影响 
			int t2=tree[1].summ;
			printf("%d\n",abs(t1-t2));
		}
	}
return 0;
}

  

1563:染色

 P2486   https://www.luogu.com.cn/problem/P2486

很好的一道树链剖分。树剖后,线段树要记录左端点l,右端点r,左端点的颜色lc,右端点的颜色rc,区间成段更新的标记tag,区间有多少颜色段。
区间合并的时候要注意如果左子树的右端和右子树的左端颜色相同那么数量要减一。
但是存在一个问题当前剖到的链与上一次的链在相交的边缘可能颜色相同,如果颜色相同答案需要减一。
所以统计答案的时候要记录下上一次剖到的链的左端点的颜色,与当前剖到的链右端点的颜色(因为在处理出的线段树中越靠近根的点位置越左)
比较这两个颜色,若相同则答案减
1。又由于有u和v两个位置在向上走,那么要记录ans1,ans2两个变量来存“上一次的左端点颜色”。有一点需要注意,当

top[u]=top[v]的时候,即已经在同一个重链上时,两边端点颜色都要考虑与对应ans比较颜色,相同答案要相应减一。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
/*
很好的一道树链剖分。树剖后,线段树要记录左端点l,右端点r,左端点的颜色lc,右端点的颜色rc,区间成段更新的标记tag,区间有多少颜色段。
区间合并的时候要注意如果左子树的右端和右子树的左端颜色相同那么数量要减一。
但是存在一个问题当前剖到的链与上一次的链在相交的边缘可能颜色相同,如果颜色相同答案需要减一。
所以统计答案的时候要记录下上一次剖到的链的左端点的颜色,与当前剖到的链右端点的颜色(因为在处理出的线段树中越靠近根的点位置越左)
比较这两个颜色,若相同则答案减
1。又由于有u和v两个位置在向上走,那么要记录ans1,ans2两个变量来存“上一次的左端点颜色”。有一点需要注意,当

top[u]=top[v]的时候,即已经在同一个重链上时,两边端点颜色都要考虑与对应ans比较颜色,相同答案要相应减一。
*/
#define L(x) (x<<1)
#define R(x) (x<<1|1)
int n,m,tot;
struct edge{
	int u,v,nex;
}ed[maxn<<1];
struct node{
	int l,r;
	int lc,rc,num,tag;
	//分别是左右孩子的颜色、颜色段、标志 
}tree[maxn<<2];
int size[maxn],dep[maxn],top[maxn],son[maxn],col[maxn],head[maxn],fa[maxn],pos[maxn];
void adde(int u,int v){
	ed[++tot].u=u;
	ed[tot].v=v;
	ed[tot].nex=head[u];
	head[u]=tot;
	ed[++tot].u=v;
	ed[tot].v=u;
	ed[tot].nex=head[v];
	head[v]=tot;
}
void dfs1(int x,int f,int deep){
	size[x]=1;
	fa[x]=f;
	dep[x]=deep;
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].v;
		if(t==f) continue;
		dfs1(t,x,deep+1);
		size[x]+=size[t];
		if(size[t]>size[son[x]]) son[x]=t;
	}
}
int dfn;
void dfs2(int x,int topf){
	top[x]=topf;
	pos[x]=++dfn; //dfs序 
	if(!son[x]) return; //叶子节点
	dfs2(son[x],topf);
	for(int i=head[x];i;i=ed[i].nex){
		int t=ed[i].v;
		if(t==fa[x]||t==son[x]) continue;
		dfs2(t,t);
	} 
}
void push_down(int x){
	if(tree[x].tag){   //向下,更新标记 
		tree[L(x)].tag=tree[R(x)].tag=tree[x].tag;
		tree[L(x)].num=tree[R(x)].num=1;
		tree[L(x)].lc=tree[L(x)].rc=tree[x].lc;  //左孩子的左右的颜色,都更新掉 
		tree[R(x)].lc=tree[R(x)].rc=tree[x].lc;  //右孩子的左右的颜色,都更新掉 
		tree[x].tag=0;
	}
} 
void push_up(int x){  //向上更新
//x的左右孩子的颜色 
	tree[x].lc=tree[L(x)].lc;
	tree[x].rc=tree[R(x)].rc;
	int ans=tree[L(x)].num+tree[R(x)].num;
	if(tree[L(x)].rc==tree[R(x)].lc) ans--;//区间合并的时候要注意如果左子树的右端和右子树的左端颜色相同那么数量要减一
	tree[x].num=ans;
}
void build(int rt,int l,int r){
	tree[rt].l=l;
	tree[rt].r=r;
	tree[rt].num=0;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(L(rt),l,mid);build(R(rt),mid+1,r);
}
void update(int rt,int l,int r,int val){  //更新区间l--r的颜色为val 
	if(tree[rt].l==l&&tree[rt].r==r){
		tree[rt].num=tree[rt].tag=1;  //只有一个颜色串了,标记也打上
		tree[rt].lc=tree[rt].rc=val;
		return; 
	} 
	push_down(rt); //破坏了
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) update(L(rt),l,r,val);
	else if(l>mid) update(R(rt),l,r,val);
	else {
		update(L(rt),l,mid,val);
		update(R(rt),mid+1,r,val);
	} 
	push_up(rt);
}
int LC,RC;
int query(int rt,int l,int r,int L,int R){//query(1,pos[top[u]],pos[u],pos[top[u]],pos[u])
	if(tree[rt].l==L) LC=tree[rt].lc;
	if(tree[rt].r==R) RC=tree[rt].rc; 
	if(tree[rt].l==l&&tree[rt].r==r) return tree[rt].num;
	push_down(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) return query(L(rt),l,r,L,R);
	else if(mid<l) return query(R(rt),l,r,L,R);
	else{
		int ans=query(L(rt),l,mid,L,R)+query(R(rt),mid+1,r,L,R);
		if(tree[L(rt)].rc==tree[R(rt)].lc) ans--;
		return ans;
	}
	push_up(rt);
}
int solve(int u,int v,int id,int c){ //id是标记 1的话是更新  2是求 
	int ans=0;
	if(id==1){
		while(top[u]!=top[v]){
			if(dep[top[u]]<dep[top[v]]) swap(u,v);
			update(1,pos[top[u]],pos[u],c);
			u=fa[top[u]];
		}
		if(dep[u]>dep[v]) swap(u,v);
		update(1,pos[u],pos[v],c);
	} 
	else{
		int ans1=-1,ans2=-1; //记录上次链的左端的颜色  
		while(top[u]!=top[v]){
			if(dep[top[u]]<dep[top[v]]) {
				swap(ans1,ans2); //????
				swap(u,v);
			}
			ans+=query(1,pos[top[u]],pos[u],pos[top[u]],pos[u]);
			if(RC==ans1) ans--;  //上一次得左孩子颜色=这一次得右孩子颜色 
			ans1=LC;
			u=fa[top[u]]; 
		}
		if(dep[u]<dep[v]) {
			swap(u,v);swap(ans1,ans2);
		}		 
		ans+=query(1,pos[v],pos[u],pos[v],pos[u]);
		if(RC==ans1) ans--;
		if(LC==ans2) ans--;
	}
	return ans;
}
char str[20];
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&col[i]);
	for(int i=1,u,v;i<n;i++){
		scanf("%d %d",&u,&v);
		adde(u,v);
	}
	dfs1(1,1,1);
	dfs2(1,1);
	build(1,1,n);
	
	//插入节点 
	for(int i=1;i<=n;i++) update(1,pos[i],pos[i],col[i]);
	
	while(m--){
		
		scanf("%s",str);
		if(str[0]=='C'){
			int u,v,c;
			scanf("%d %d %d",&u,&v,&c);
			solve(u,v,1,c);
		}
		else{
			int u,v;
			scanf("%d %d",&u,&v);
			printf("%d\n",solve(u,v,2,0));
		}
	}
return 0;
}

  

 

1564:「SDOI2014」旅行

每个点有了1个新的标记:信仰得宗教,一个人只会在同种信仰里面留宿(停留)
//这道题得操作是:点修改、区间查询总和、最大值

要点:

1.有多少个宗教,就有多少个线段树。------>这个我没有想到

2.线段树需要维护两个值:区间和值,区间最值,此处 区间 由 相同宗教的城市 组成。

3.线段树除了 插入值 ,还要 删除值;当树中没有某个节点时,增加新节点,否则直接修改。 -----》》删除值?我也没有想到 ,因为在中间会修改节点的宗教,所以在不同的宗教线段树
上面就需要加入节点或者删除节点

#include<iostream>
#include<algorithm>
#include<cstdio>
#define MAXN 100010
using namespace std;
int n,m,d=1,e=1,g=1;
int c[MAXN],w[MAXN],root[MAXN];
int head[MAXN],id[MAXN],top[MAXN],deep[MAXN],fa[MAXN],son[MAXN],num[MAXN];
struct node1{//结构体前向星
    int next,to;
}a[MAXN<<1];
struct node2{//动态线段树
    int l,r,data1,data2;
}b[MAXN*20];
inline int read(){//弱弱的读优
    int date=0,w=1;char c=0;
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    return date*w;
}
inline int max(const int &x,const int &y){//手写 max ,感觉有点手残。。。
    if(x>y)return x;
    return y;
}
void pushup(int rt){//上传
    b[rt].data1=b[b[rt].l].data1+b[b[rt].r].data1;
    b[rt].data2=max(b[b[rt].l].data2,b[b[rt].r].data2);
}
void pushdown(int rt){//清空
    b[rt].data1=b[rt].data2=b[rt].l=b[rt].r=0;
}
void insert(int k,int v,int l,int r,int &rt){//插入
    int mid;
    if(!rt)rt=e++;//如上 第3点
    if(l==v&&v==r){
        b[rt].data1=b[rt].data2=k;
        return;
    }
    mid=l+r>>1;
    if(v<=mid)insert(k,v,l,mid,b[rt].l);
    else insert(k,v,mid+1,r,b[rt].r);
    pushup(rt);
}
void remove(int k,int l,int r,int &rt){//删除
    int mid;
    if(l==r){
        pushdown(rt);
        rt=0;
        return;
    }
    mid=l+r>>1;
    if(k<=mid)remove(k,l,mid,b[rt].l);
    else remove(k,mid+1,r,b[rt].r);
    pushup(rt);
    if(!b[rt].l&&!b[rt].r){//注意这里,左子树 与 右子树 都空时,节点为空
        pushdown(rt);
        rt=0;
    }
}
int query1(int s,int t,int l,int r,int rt){//区间求和
    if(!rt)return 0;//节点为空,返回
    int mid;
    if(l==s&&r==t)
    return b[rt].data1;
    mid=l+r>>1;
    if(t<=mid)return query1(s,t,l,mid,b[rt].l);
    else if(s>mid)return query1(s,t,mid+1,r,b[rt].r);
    else return query1(s,mid,l,mid,b[rt].l)+query1(mid+1,t,mid+1,r,b[rt].r);
}
int query2(int s,int t,int l,int r,int rt){//区间求最值
    if(!rt)return 0;
    int mid;
    if(l==s&&r==t)
    return b[rt].data2;
    mid=l+r>>1;
    if(t<=mid)return query2(s,t,l,mid,b[rt].l);
    else if(s>mid)return query2(s,t,mid+1,r,b[rt].r);
    else return max(query2(s,mid,l,mid,b[rt].l),query2(mid+1,t,mid+1,r,b[rt].r));
}
void add(int x,int y){//加边
    a[d].to=y;
    a[d].next=head[x];
    head[x]=d++;
    a[d].to=x;
    a[d].next=head[y];
    head[y]=d++;
}
void buildtree(int rt){//建树+树剖准备1
    int will;
    num[rt]=1;
    for(int i=head[rt];i;i=a[i].next){
        will=a[i].to;
        if(!deep[will]){
            deep[will]=deep[rt]+1;
            fa[will]=rt;
            buildtree(will);
            num[rt]+=num[will];
            if(num[will]>num[son[rt]])son[rt]=will;
        }
    }
}
void dfs(int rt,int fa){//树剖准备2
    if(son[rt]){
        top[son[rt]]=top[rt];
        id[son[rt]]=++g;
        dfs(son[rt],rt);
    }
    int v;
    for(int i=head[rt];i;i=a[i].next){
        v=a[i].to;
        if(v==fa||v==son[rt])continue;
        top[v]=v;
        id[v]=++g;
        dfs(v,rt);
    }
}
void change1(int x,int y){//修改宗教:原宗教中删除,新宗教中插入
    remove(id[x],1,n,root[c[x]]);
    c[x]=y;
    insert(w[x],id[x],1,n,root[c[x]]);
}
void change2(int x,int y){//修改评价:直接插入
    w[x]=y;
    insert(w[x],id[x],1,n,root[c[x]]);
}
void work1(int x,int y){//求评价和
    int cs=c[x],s=0;
    while(top[x]!=top[y]){//树剖搞起
        if(deep[top[x]]<deep[top[y]])swap(x,y);
        s+=query1(id[top[x]],id[x],1,n,root[cs]);
        x=fa[top[x]];
    }
    if(deep[x]>deep[y])swap(x,y);
    s+=query1(id[x],id[y],1,n,root[cs]);//不要忘了这里。。。
    printf("%d\n",s);
}
void work2(int x,int y){//求评价最值
    int cs=c[x],s=0;
    while(top[x]!=top[y]){//同上
        if(deep[top[x]]<deep[top[y]])swap(x,y);
        s=max(s,query2(id[top[x]],id[x],1,n,root[cs]));
        x=fa[top[x]];
    }
    if(deep[x]>deep[y])swap(x,y);
    s=max(s,query2(id[x],id[y],1,n,root[cs]));
    printf("%d\n",s);
}
int main(){
    int x,y;
    char ch[3];
    n=read();m=read();
    for(int i=1;i<=n;i++){w[i]=read();c[i]=read();}
    for(int i=1;i<n;i++){
        x=read();y=read();
        add(x,y);
    }
    deep[1]=id[1]=top[1]=1;//初值
    buildtree(1);
    dfs(1,0);
    for(int i=1;i<=n;i++)insert(w[i],id[i],1,n,root[c[i]]);//建初始线段树
    while(m--){//主过程
        scanf("%s",ch);x=read();y=read();
        if(ch[0]=='C'){
            if(ch[1]=='C')change1(x,y);
            if(ch[1]=='W')change2(x,y);
        }
        if(ch[0]=='Q'){
            if(ch[1]=='S')work1(x,y);
            if(ch[1]=='M')work2(x,y);
        }
    }
    return 0;
}

  

 

 posted on 2020-10-04 23:31  shirlybabyyy  阅读(133)  评论(0编辑  收藏  举报