雨天的尾巴——LCA+树上差分+动态开点+线段树合并

雨天的尾巴

题目背景

深绘里一直很讨厌雨天。

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

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

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

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

题目描述

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

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

输入格式

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

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

( n + 1 ) (n + 1) (n+1) 到第 ( n + m ) (n + m) (n+m) 行,每行有三个用空格隔开的整数 x ,   y ,   z x,~y,~z x, y, z,代表一次救济粮的发放是从 x x x y y y 路径上的每栋房子发放了一袋 z z z 类型的救济粮。

输出格式

输出 n n n 行,每行一个整数,第 i i i 行的整数代表 i i i 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。

如果某座房屋没有救济粮,则输出 0 0 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 % 20\% 20% 的数据,保证 n , m ≤ 100 n, m \leq 100 n,m100
  • 对于 50 % 50\% 50% 的数据,保证 n , m ≤ 2 × 1 0 3 n, m \leq 2 \times 10^3 n,m2×103
  • 对于 100 % 100\% 100% 测试数据,保证 1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^5 1n,m105 1 ≤ a , b , x , y ≤ n 1 \leq a,b,x,y \leq n 1a,b,x,yn 1 ≤ z ≤ 1 0 5 1 \leq z \leq 10^5 1z105

题解

对于一次操作a,b,z。我们求出a,b的lca,一个很暴力的想法是,我们开一个二维数组g[i][j],利用树上差分,我们使g[a][z]+1,g[b][z]+1,g[lca(a,b)]-1,g[fa[lca(a,b)]]-1。最后dfs自底向上求和,此时的g[i][j]就是点i有多少个j。题目最后要我们求每个点最多的物品是什么,就是求一个序列的最大值。如果一点优化没有的话,时空复杂度是n2,时间复杂度是n2

接下来考虑如何优化。
1.如何加速求一个序列的最大值:可以使用线段树进行维护,复杂度是nlogn。
2.如何减小空间复杂度。我们如果为1e5个点都开一个区间长度为1e5的线段树,空间肯定会爆炸,但是我们发现我们并不需要每个点都开一个完整的线段树,我们只开辟用到的节点即可,所以我们可以用动态开点的方式构建线段树,现在我们分析一下空间复杂度,我们一共有1e5*4次操作,每次操作最多开辟logn个节点≈17。故我们的节点数最多为17*4e5,大约90M左右。
3.如果使用动态开点的线段树,如何计算最后的答案:我们还是遵循最初的分析,使用dfs自底向上求和,对应到线段树就是自底向上进行合并。进行合并时,线段树两两合并最后变为一颗线段树,合并次数不会超过所有线段树的节点个数+1。故时间复杂度是nlogn。

附上代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,M = N<<1;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n,m;
int depth[N],f[N][17],q[N];
int fa[N],ans[N];
void bfs(){
    memset(depth,0x3f,sizeof depth);
    depth[0]=0,depth[1]=1;
    int hh=0,tt=0;
    q[tt++]=1;
    while(hh!=tt){
        int t=q[hh++];
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(depth[j]>depth[t]+1){
                fa[j]=t;
                depth[j]=depth[t]+1;
                q[tt++]=j;
                f[j][0]=t;
                for(int k=1;k<17;k++)f[j][k]=f[f[j][k-1]][k-1];
            }
        }
    }
}
int lca(int a,int b){
    if(depth[a]<depth[b])swap(a,b);
    for(int i=16;i>=0;i--)
        if(depth[f[a][i]]>=depth[b])a=f[a][i];
    if(a==b)return a;
    for(int i=16;i>=0;i--){
        if(f[a][i]!=f[b][i]){
            a=f[a][i],b=f[b][i];
        }
    }
    return f[a][0];
}
int tot,root[N];
struct Node{
    int lc,rc;
    int mx,pos;
}tr[N*70];
int build(){
    tot++;
    tr[tot].lc=tr[tot].rc=tr[tot].mx=0;
    return tot;
}
void pushup(int p){
    if(tr[tr[p].lc].mx>=tr[tr[p].rc].mx){
        tr[p].mx=tr[tr[p].lc].mx;
        tr[p].pos=tr[tr[p].lc].pos;
    }
    else{
        tr[p].mx=tr[tr[p].rc].mx;
        tr[p].pos=tr[tr[p].rc].pos;    
    }
}
void insert(int p,int l,int r,int k,int v,int rt){
    //if(rt==3)cout<<l<<" "<<r<<" "<<k<<endl;
    if(l==r){
        tr[p].mx+=v;
        tr[p].pos=l;
        //if(rt==3)cout<<tr[p].mx<<"@@@"<<tr[p].pos<<endl;
        return;
    }
    int mid=l+r>>1;
    if(k<=mid){
        if(!tr[p].lc)tr[p].lc=build();
        insert(tr[p].lc,l,mid,k,v,rt);
    }
    else{
        if(!tr[p].rc)tr[p].rc=build();
        insert(tr[p].rc,mid+1,r,k,v,rt);
    }
    pushup(p);
    //if(rt==3)cout<<tr[p].pos<<endl;
}
int merge(int p,int q,int l,int r){
    if(!p)return q;
    if(!q)return p;
    if(l==r){
        tr[p].pos=l;
        tr[p].mx+=tr[q].mx;
        return p;
    }
    int mid=l+r>>1;
    tr[p].lc=merge(tr[p].lc,tr[q].lc,l,mid);
    tr[p].rc=merge(tr[p].rc,tr[q].rc,mid+1,r);
    pushup(p);
    return p;
}
void dfs(int u){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==fa[u])continue;
        dfs(j);
        root[u]=merge(root[u],root[j],1,100000);
    }
    if(tr[root[u]].mx)ans[u]=tr[root[u]].pos;
    else ans[u]=0;
}
int main(){
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    bfs();
    for(int i=0;i<=n;i++){
        root[i]=build();
    }
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        int lc=lca(a,b);
        insert(root[a],1,100000,c,1,root[a]);
        insert(root[b],1,100000,c,1,root[b]);
        insert(root[lc],1,100000,c,-1,root[lc]);
        insert(root[fa[lc]],1,100000,c,-1,root[fa[lc]]);
    }
    dfs(1);
    for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值