洛谷P4556 雨天的尾巴
题目链接: P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并
题目大意:
有一颗 \(n\) 个节点的树,\(m\) 次操作,每次将节点 \(u\) 到 \(v\) 的路径上的每个点放一个物品 \(c\) ,最后询问每个节点上数量最多的物品是什么,其中数量相同的物品取编号最小者,若无物品输出0。
\(n,m,c\leq 10^5\)
思路:
如题目名称,模板题,蒟蒻之前没写过线段树合并,就简单总结一下吧。
线段树合并有两种写法:
1.将 \(b\) 直接合并到 \(a\) 上去:
int merge(int a,int b){
if(!a)return b;
if(!b)return a;
if(t[a].l==t[a].r){
//对线段树储存信息的叶节点进行具体的合并
}else{
t[a].ls=merge(t[a].ls,t[b].ls);
t[a].rs=merge(t[a].rs,t[b].rs);
update(a);
}
return a;
}
这种写法会使 \(a\) 直接继承了部分 \(b\) 的节点,所以之后对 \(a\) 进行其他的修改时,\(b\) 也会被改掉,因此这么写只适用与离线查询(如此题)。
2.新建节点储存合并后的树
int merge(int a,int b){
if(!a)return b;
if(!b)return a;
int root=++cnt;
if(t[a].l==t[a].r){
t[root]=....;//进行具体的信息合并
}else{
t[root].ls=merge(t[a].ls,t[b].ls);
t[root].rs=merge(t[a].rs,t[b].rs);
update(root);
}
return root;
}
这样写就可以适用于动态查询,其缺点是空间复杂度比较大,在离线查询时线段树的空间约为前者的两倍。
对于这道题,我们考虑树上差分,对于每一个节点建一颗动态开点权值线段树,然后dfs时节点和自己的所有儿子合并即可。
细节:
- 如果写的是第一种合并方式,那么每个节点需要在回溯前将答案保存到 \(ans_i\) 中,因为之后该节点上的线段树可能会被其祖先修改。
- 尝试将 \(Tarjan\) 求 \(LCA\) 和 \(dfs\) 写到一起是一件很荒谬的事情,计算答案的先后顺序会出大问题。
- 日常:当 \(LCA\) 的 \(a\) 和 \(b\) 是同一个节点时,只记录一次。
Code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<fstream>
#define N 100100
#define R 100000
using namespace std;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
struct tree{
int l,r;
int mx,node;
int ls,rs;
}t[17*4*N];
int head[N],to[N*2],nxt[N*2];
int cnt_n,cnt_e;
int root[N];
int ancest[N],fa[N];
int ans[N];
vector<pair<int,int> > upd[N];
bool vis[N];
void init(int n){
cnt_e=-1;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)fa[i]=i;
}
void add_e(int a,int b,bool id){
nxt[++cnt_e]=head[a];
head[a]=cnt_e;
to[cnt_e]=b;
if(id)add_e(b,a,0);
}
void update(int x){
int a=t[t[x].ls].mx,b=t[t[x].rs].mx;
int c=t[t[x].ls].node,d=t[t[x].rs].node;
t[x].mx=max(a,b);
if(t[x].rs==0||a>b||(a==b&&c<d))t[x].node=c;
else t[x].node=d;
}
void insert(int &a,int l,int r,int pos,int k){
if(!a)a=++cnt_n,t[a].l=l,t[a].r=r;
if(l==r){
t[a].mx+=k,t[a].node=l;return;
}
int mid=(l+r)>>1;
if(mid>=pos)insert(t[a].ls,l,mid,pos,k);
else insert(t[a].rs,mid+1,r,pos,k);
update(a);
}
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int merge(int a,int b){
if(!a)return b;
if(!b)return a;
int l=t[a].l,r=t[a].r;
if(l==r){
t[a].mx+=t[b].mx;
}else{
t[a].ls=merge(t[a].ls,t[b].ls);
t[a].rs=merge(t[a].rs,t[b].rs);
update(a);
}
return a;
}
void tarjan(int x,int fath){
ancest[x]=fath;
for(int i=head[x];~i;i=nxt[i]){
if(vis[to[i]]||to[i]==fath)continue;
tarjan(to[i],x);
}
vis[x]=true;
for(int i=upd[x].size()-1;i>=0;i--){
int y=upd[x][i].first,z=upd[x][i].second;
if(vis[y]){
insert(root[y],1,R,z,1);
insert(root[x],1,R,z,1);
int lca=find(y);
insert(root[lca],1,R,z,-1);
insert(root[ancest[lca]],1,R,z,-1);
}
}
fa[x]=fath;
}
void dfs(int x){
for(int i=head[x];~i;i=nxt[i]){
if(to[i]==ancest[x])continue;
dfs(to[i]);
root[x]=merge(root[x],root[to[i]]);
}
ans[x]=t[root[x]].mx>0?t[root[x]].node:0;
}
int main(){
int n,m;
int x,y,z;
cin>>n>>m;
init(n);
for(int i=1;i<n;i++){
add_e(read(),read(),1);
}
for(int i=0;i<m;i++){
x=read(),y=read(),z=read();
upd[x].push_back(make_pair(y,z));
if(x!=y)upd[y].push_back(make_pair(x,z));
}
tarjan(1,0);
dfs(1);
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}