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

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

题目背景

深绘里一直很讨厌雨天。

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

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

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

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

题目描述

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

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

输入格式

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

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

\((n + 1)\) 到第 \((n + m)\) 行,每行有三个用空格隔开的整数 \(x, y, z\),代表一次救济粮的发放是从 \(x\)\(y\) 路径上的每栋房子发放了一袋 \(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, m \leq 100\)
  • 对于 \(50\%\) 的数据,保证 \(n, m \leq 2 \times 10^3\)
  • 对于 \(100\%\) 测试数据,保证 \(1 \leq n, m \leq 10^5\)\(1 \leq a,b,x,y \leq n\)\(1 \leq z \leq 10^5\)

链接

假设粮食的种类数量为\(k\)

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

这里需要用到线段树合并。
通过线段树合并来快速统合两个不同节点上的信息,并查询需要的答案。
这里面就是对每个点,将树上差分所产生的不同种类的\(+1\)\(-1\)放入一个大小为\(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 @ 2024-07-22 15:36  HL_ZZP  阅读(5)  评论(0编辑  收藏  举报