洛谷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;
}
posted @ 2021-01-09 11:23  Neal_lee  阅读(111)  评论(0编辑  收藏  举报