洛谷 P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并
题目背景
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有 nn 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 (x,~y)(x, y),然后对于 xx 到 yy 的路径上(含 xx 和 yy)每座房子里发放一袋 zz 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入格式
输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 nn 和救济粮发放的次数 mm。
第 22 到 第 nn 行,每行有两个用空格隔开的整数 a,~ba, b,代表存在一条连接房屋 aa 和 bb 的边。
第 (n + 1)(n+1) 到第 (n + m)(n+m) 行,每行有三个用空格隔开的整数 x,~y,~zx, y, z,代表一次救济粮的发放是从 xx 到 yy 路径上的每栋房子发放了一袋 zz 类型的救济粮。
输出格式
输出 nn 行,每行一个整数,第 ii 行的整数代表 ii 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 00。
题解:
一开始考虑的是类比SDOI2014旅行的做法,对于(z)颜色每种颜色都开一棵动态开点线段树。但是这道题需要维护的东西却不能通过这种做法很好地维护出来。怎么办呢?
这种维护种类编号的题,我们可以考虑用权值线段树来维护。这样的话,我们在每个线段树节点维护两个信息,一个是最大值,一个是最大值编号,就可以很方便地来维护。
但是这样的话,我们需要在每个节点开一棵权值线段树,如果我们暴力地用树剖序去更新权值线段树的话,复杂度肯定是受不了。所以我们进一步观察性质:
很多次修改,一次查询。
差分啊!
所以我们的每次询问只需要用树上差分,只需要有4次操作即可。
最后的树上差分统计答案,因为在每个节点运用了权值线段树,所以要有权值线段树的合并。
关于线段树合并:
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0,f=1;
char ch=nc();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=nc();
}
while(ch>='0'&&ch<='9')
x=x*10+ch-'0',ch=nc();
return x*f;
}
const int maxn=1e5+10;
int n,m,maxx;
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int deep[maxn],size[maxn],fa[maxn],top[maxn],son[maxn],root[maxn];
int lson[maxn*60],rson[maxn*60],sum[maxn*60],wh[maxn*60],cnt;
//sum存最多救济粮个数,wh存最小编号的最多救济粮种类
int ans[maxn];
struct node
{
int x,y,z;
}q[maxn];
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int x,int f)
{
deep[x]=deep[f]+1;
fa[x]=f;
size[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==f)
continue;
dfs1(y,x);
size[x]+=size[y];
if(!son[x]||size[y]>size[son[x]])
son[x]=y;
}
}
void dfs2(int x,int t)
{
top[x]=t;
if(!son[x])
return;
dfs2(son[x],t);
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x]||y==son[x])
continue;
dfs2(y,y);
}
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])
swap(x,y);
x=fa[top[x]];
}
return deep[x]<deep[y]?x:y;
}
void pushup(int pos)
{
if(sum[lson[pos]]>=sum[rson[pos]])
sum[pos]=sum[lson[pos]],wh[pos]=wh[lson[pos]];
else
sum[pos]=sum[rson[pos]],wh[pos]=wh[rson[pos]];
}
void update(int &pos,int l,int r,int x,int k)//将数x的个数+k
{
int mid=(l+r)>>1;
if(!pos)
pos=++cnt;
{
sum[pos]+=k;
wh[pos]=l;
return;
}
if(x<=mid)
update(lson[pos],l,mid,x,k);
else
update(rson[pos],mid+1,r,x,k);
pushup(pos);
}
void merge(int &x,int y,int l,int r)
{
int mid=(l+r)>>1;
if(!x||!y)
{
x=(!x?y:x);
return;
}
if(l==r)
{
sum[x]+=sum[y];
wh[x]=l;
return;
}
merge(lson[x],lson[y],l,mid);
merge(rson[x],rson[y],mid+1,r);
pushup(x);
}
void dfs(int x)
{
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x])
continue;
dfs(y);
merge(root[x],root[y],1,maxx);
}
if(sum[root[x]])
ans[x]=wh[root[x]];
}
int main()
{
n=read();m=read();
for(int i=1;i<n;i++)
{
int x,y;
x=read();y=read();
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=m;i++)
{
q[i].x=read(),q[i].y=read(),q[i].z=read();
maxx=max(maxx,q[i].z);
}
for(int i=1;i<=m;i++)//树上的每一个节点有一棵权值线段树
{
int l=lca(q[i].x,q[i].y);
update(root[q[i].x],1,maxx,q[i].z,1);
update(root[q[i].y],1,maxx,q[i].z,1);
update(root[l],1,maxx,q[i].z,-1);
if(fa[l])
update(root[fa[l]],1,maxx,q[i].z,-1);
}
dfs(1);
for(int i=1;i<=n;i++)
printf("%d
",ans[i]);
return 0;
}