[Vani有约会] 雨天的尾巴 /【模板】线段树合并

[Vani有约会] 雨天的尾巴 /【模板】线段树合并

题目背景

深绘里一直很讨厌雨天。

灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。

虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。

无奈的深绘里和村民们只好等待救济粮来维生。

不过救济粮的发放方式很特别。

题目描述

首先村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 m 次发放,每次选择两个房屋 (x,y),然后对于 xy 的路径上(含 xy)每座房子里发放一袋 z 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

输入格式

输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 n 和救济粮发放的次数 m

2 到 第 n 行,每行有两个用空格隔开的整数 a,b,代表存在一条连接房屋 ab 的边。

(n+1) 到第 (n+m) 行,每行有三个用空格隔开的整数 x,y,z,代表一次救济粮的发放是从 xy 路径上的每栋房子发放了一袋 z 类型的救济粮。

输出格式

输出 n 行,每行一个整数,第 i 行的整数代表 i 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。

如果某座房屋没有救济粮,则输出 0

样例 #1

样例输入 #1

5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3

样例输出 #1

2
3
3
0
2

提示

  • 对于 20% 的数据,保证 n,m100
  • 对于 50% 的数据,保证 n,m2×103
  • 对于 100% 测试数据,保证 1n,m1051a,b,x,yn1z105

链接

假设粮食的种类数量为k

树上的路径区间加法操作,很明显是树上差分,但是对于不同的种类,还需要分来计算,再统计最大值。
一个思路就是对每个点开一个数组,然后用lca完成区间加法,再对不同种类查询k次,总复杂度为O(nk+nlogn)
kn是同一个数量级的,自然是无法接受的。

这里需要用到线段树合并。
通过线段树合并来快速统合两个不同节点上的信息,并查询需要的答案。
这里面就是对每个点,将树上差分所产生的不同种类的+11放入一个大小为k的线段树中,这个线段树只需要维护里面所有数字的最大值即可。而k次的树上累加来求差分值,则变成了从底部向上的线段树合并,合并的同时是可以保证线段树的结构来快速查询最大值的。

所以就没了
这里也非常体现了动态开点和线段树合并的优越性,一个大大降低了空间复杂度,很大程度上减少了多棵线段树时的无用点,一个则是在O(nlogn)的时间内统合了原本用O(nk)的时间才能够计算的东西,也就是合并了n个节点较少的线段树。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
struct edge
{
	int next,to;
}e[200001];
int head[200001],tot,dep[100001][21],f[100001][21],n,m,ans[100001],Right,st[100001];
struct Ask
{
	int x,y,z;
}ques[100001];
struct Segment_Tree
{
	int l,r,Max,Kind;
}tr[6000001];
int p;
inline void add(int i,int j)
{
	e[++tot].next=head[i];
	e[tot].to=j;
	head[i]=tot;
}
void dfs1(int x,int fa,int now)
{
//	cout<<x<<endl;
	f[x][0]=fa;dep[x][0]=now-1;
	for(int i=head[x];i!=0;i=e[i].next)
	{
		int u=e[i].to;
		if(u==fa)continue;
		dfs1(u,x,now+1);
	}
}
int Get_Lca(int x,int y)
{
	if(dep[x][0]<dep[y][0])swap(x,y);
	int now=20;
	while(now>=0)
	{
		if(dep[x][now]>dep[y][0])x=f[x][now];
		now--;
	}
	if(x==y)return x;
	now=20;
	while(now>=0)
	{
		if(f[x][now]!=f[y][now])x=f[x][now],y=f[y][now];
		now--;
	}
	return f[x][0];
}
inline void push_up(int x)
{
	if(tr[tr[x].l].Max>=tr[tr[x].r].Max)
	tr[x].Max=tr[tr[x].l].Max,tr[x].Kind=tr[tr[x].l].Kind;
	else 
	tr[x].Max=tr[tr[x].r].Max,tr[x].Kind=tr[tr[x].r].Kind;
}
int change(int now,int x,int y,int Ned,int val)
{
	if(now==0)now=++p;
	if(x==y)
	{
		tr[now].Max+=val;
		tr[now].Kind=x;
		return now;
	}
	int mid=x+y>>1;
	if(Ned>=mid+1)tr[now].r=change(tr[now].r,mid+1,y,Ned,val);
	else tr[now].l=change(tr[now].l,x,mid,Ned,val);
	push_up(now);
	return now;
}
bool flag;
int merge(int l,int r,int x,int y)
{
//	if(flag)cout<<x<<' '/*<<mid*/<<' '<<y<<endl;
	if(l==0)return r;if (r==0) return l;
	if(x==y)
	{
		tr[l].Max+=tr[r].Max;
		tr[l].Kind=x;
		return l;
	}
	int mid=x+y>>1;
	tr[l].l=merge(tr[l].l,tr[r].l,x,mid);
	tr[l].r=merge(tr[l].r,tr[r].r,mid+1,y);
	push_up(l);
	return l;
}
void dfs2(int x,int fa)
{
	for(int i=head[x];i!=0;i=e[i].next)
	{
		int u=e[i].to;
		if(u==fa)continue;
		dfs2(u,x);
//		if(x==1&&u==3)flag=1;
		st[x]=merge(st[x],st[u],1,Right);
//		if(x==1)flag=0;
//		push_up(st[x]);
	}
//	if(x==1)
//	{
//		cout<<tr[st[x]].Kind<<endl;
//	}
//	cout<<x<<' '<<tr[st[x]].Max<<' '<<tr[st[x]].Kind<<endl;
	if(tr[st[x]].Max!=0)ans[x]=tr[st[x]].Kind;
}
int main()
{
//	freopen("1.in","r",stdin);
	n=read();m=read();
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	dfs1(1,0,1);
	for(int j=0;j<=19;j++)
	{
		for(int i=1;i<=n;i++)
		{
			f[i][j+1]=f[f[i][j]][j];
			dep[i][j+1]=dep[f[i][j]][j];
		}
	}
	for(int i=1;i<=m;i++)
	{
		ques[i].x=read(),ques[i].y=read(),ques[i].z=read();
		Right=max(Right,ques[i].z);
	}
	for(int i=1;i<=m;i++)
	{
		int Lca=Get_Lca(ques[i].x,ques[i].y);
//		cout<<Lca<<' ';
		st[ques[i].x]=change(st[ques[i].x],1,Right,ques[i].z,1);
		st[ques[i].y]=change(st[ques[i].y],1,Right,ques[i].z,1);
		st[Lca]=change(st[Lca],1,Right,ques[i].z,-1);
		if(f[Lca][0]!=0)st[f[Lca][0]]=change(st[f[Lca][0]],1,Right,ques[i].z,-1);
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
//		cout<<tr[st[i]].Max<<' ';
		cout<<ans[i]<<endl;
	}
	return 0;
}

线段树合并的使用要求就是查询的信息和修改的操作要满足分配律。而一般则是用来实现较大面积的信息结合。。有点抽象,因为我不知道怎么描述,没写过多少题,后面总结的就清晰了。

posted @   HL_ZZP  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示