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