雨天的尾巴「线段树合并+树上差分」

雨天的尾巴「线段树合并+树上差分」

题目描述(简化版)

\(N\) 个点,形成一个树状结构。有 \(M\) 次发放,每次选择两个点 \(x,y\) 对于 \(x\)\(y\) 的路径上(含 \(x,y\))每个点发一袋 \(Z\) 类型的物品。完成所有发放后,每个点存放最多的是哪种物品。

输入格式

第一行数字 \(N,M\)
接下来 \(N-1\) 行,每行两个数字 \(a,b\),表示 \(a\)\(b\) 间有一条边
再接下来 \(M\) 行,每行三个数字 \(x,y,z\).如题

输出格式

输出有 \(N\)
\(i\) 行的数字表示第 \(i\) 个点存放最多的物品是哪一种,如果有多种物品的数量一样,输出编号最小的。如果某个点没有物品则输出 \(0\)

思路分析

板子题

  • 首先发现这是在树上进行区间修改,所以可以用树上差分来处理
  • 每一个节点都要记录不同种类的物品的个数,因此每一个节点都开一个权值线段树,以物品种类编号为下标
  • 因为使用了树上差分,所以每一个节点的线段树都要与其子树内的所有节点的线段树合并,类似前缀和思想,这样就可以得到该节点最终的线段树,就可以更新答案了

详见代码

\(Code\)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100005
#define M 6000005
#define R register
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,m,ed,head[N],siz[N],dep[N],f[N],son[N],top[N];
int tr[M],mx[M],ls[M],rs[M];//tr记录的是线段树中每个物品的种类数,mx记录的是种类数最多的物品
int ans[N],cnt,x[N],y[N],z[N],rt[N]; //rt表示每个结点的线段树的根节点
struct edge{
	int to,next;
}e[N<<1];
int len;
void addedge(int u,int v){
	e[++len].to = v;
	e[len].next = head[u];
	head[u] = len;
}
void dfs1(int u,int fa){ //树剖求LCA
	siz[u] =  1;
	dep[u] = dep[fa]+1;
	f[u] = fa;
	for(R int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(v==fa)continue;
		dfs1(v,u);
		siz[u] += siz[v];
		if(siz[v]>siz[son[u]])son[u] = v;
	}
}
void dfs2(int u,int tp){
	top[u] = tp;
	if(son[u])dfs2(son[u],tp);
	for(R int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(v!=son[u]&&v!=f[u])dfs2(v,v);
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x = f[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}//————————————手动分割线——————————————

//以下为线段树操作
void pushup(int rt){
    if(tr[ls[rt]]>=tr[rs[rt]])tr[rt] = tr[ls[rt]],mx[rt] = mx[ls[rt]];
    else tr[rt] = tr[rs[rt]],mx[rt] = mx[rs[rt]];
}
int modify(int rt,int l,int r,int pos,int val){
	if(!rt) rt = ++cnt; //动态开点
	if(l==r){
		tr[rt] += val;
		mx[rt] = l;
		return rt;
	}
	int mid = (l+r)>>1;
	if(pos<=mid)ls[rt] = modify(ls[rt],l,mid,pos,val);
	else rs[rt] = modify(rs[rt],mid+1,r,pos,val);
	pushup(rt);
	return rt;
}
int merge(int a,int b,int l,int r){ //线段树合并
	if(!a)return b;
	if(!b)return a;
	if(l==r){
		tr[a] += tr[b];
		mx[a] = l;
		return a;
	}
	int mid = (l+r)>>1;
	ls[a] = merge(ls[a],ls[b],l,mid),rs[a] = merge(rs[a],rs[b],mid+1,r);
	pushup(a);
	return a;
}
void get_ans(int u){//dfs统计差分答案
	for(R int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(dep[v]>dep[u]){
			get_ans(v);
			rt[u] = merge(rt[u],rt[v],1,ed);
		}
	}
	if(tr[rt[u]])ans[u] = mx[rt[u]];
}
int main(){
	n = read(),m = read();
	for(R int i = 1;i < n;i++){
		int a = read(),b = read();
		addedge(a,b),addedge(b,a);
	}
	dfs1(1,0),dfs2(1,1);//注意是dfs1(1,0)而不是dfs1(1,1),因为这个坑花了多长时间就不提了:-)
	for(R int i = 1;i <= m;i++){
		x[i] = read(),y[i] = read(),z[i] = read();
		ed = max(ed,z[i]);
	}
	for(R int i=1;i<=m;i++){//差分处理
		int lca=LCA(x[i],y[i]);
		rt[x[i]]=modify(rt[x[i]],1,ed,z[i],1),rt[y[i]]=modify(rt[y[i]],1,ed,z[i],1);
		rt[lca]=modify(rt[lca],1,ed,z[i],-1);
		if(f[lca]) rt[f[lca]]=modify(rt[f[lca]],1,ed,z[i],-1);
        }
	get_ans(1);
	for(R int i = 1;i <= n;i++)printf("%d\n",ans[i]);
	return 0;
}
posted @ 2020-09-09 11:01  HH_Halo  阅读(180)  评论(0编辑  收藏  举报