【GDOI2014模拟】雨天的尾巴

Description

给出一个n个节点的树和m次操作,每次操作把x到y的路径上的所有的点的z种物品+1.
求最后每个点最多的物品编号。若有多个相同的取编号小的,若没有则输出0.
n,m<=10^5,z<=10^9

Solution

首先,这道题并没有在线和离线之分,所以可以任意乱搞。
我们把所有z离散化,这样就只有m种取值。
那么对于一次操作,我们相当于做一次线段覆盖,在x+1,y+1,lca-1, fa[lca]-1.
我们把所有操作挂到树上,从底往上做。
每个点的值相当于它所有儿子的和+它自己的东西。
那么我们可以开一颗权值线段树来维护这个东西。
首先把儿子都合并,再更新自己的。
当然,树链剖分也是可以做的,不过带两个log。
为毛一个log比两个log慢!

Code

#include<map>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 100005
using namespace std;
map<int,int> h;
vector<int> a[N],b[N];
struct note{int mx,id;}tr[N*50];
int n,m,x,y,z,l,tot,top,root[N],le[N*50],ri[N*50],bz[N*50];
int t[N*2],next[N*2],last[N],p[N],d[N],ans[N],fa[N][17];
void add(int x,int y) {
    t[++l]=y;next[l]=last[x];last[x]=l;
}
void dfs(int x,int y) {
    d[x]=d[y]+1;fa[x][0]=y;
    rep(i,x) if (t[i]!=y) dfs(t[i],x);
}
int lca(int x,int y) {
    if (d[x]<d[y]) swap(x,y);
    fd(j,16,0) if (d[fa[x][j]]>d[y]) x=fa[x][j];
    if (d[x]>d[y]) x=fa[x][0];
    fd(j,16,0) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
    if (x!=y) return fa[x][0];else return x; 
}
note max(note y,note z) {
    note x;
    if (y.mx==z.mx) {x.mx=y.mx;x.id=min(y.id,z.id);} 
    else if (y.mx>z.mx) {x.mx=y.mx;x.id=y.id;} 
    else {x.mx=z.mx;x.id=z.id;}
    return x;
}
void change(int &v,int l,int r,int x,int y) {
    if (!v) {v=++top;}int m=(l+r)/2;
    if (l==r) {
        tr[v].mx+=y;tr[v].id=p[l];
        if (tr[v].mx<0) tr[v].mx=tr[v].id=0;
        return;
    } 
    if (x<=m) change(le[v],l,m,x,y);
    else change(ri[v],m+1,r,x,y);
    tr[v]=max(tr[le[v]],tr[ri[v]]);
}
int merge(int v,int x,int l,int r) {
    if (!v||!x) return v+x;
    if (l==r) {
        tr[v].mx+=tr[x].mx;
        return v;
    }
    int m=(l+r)/2;
    le[v]=merge(le[v],le[x],l,m);
    ri[v]=merge(ri[v],ri[x],m+1,r);
    tr[v]=max(tr[le[v]],tr[ri[v]]);
    return v;
}
void make(int x,int y) {
    rep(i,x) if (t[i]!=y) make(t[i],x),
    root[x]=merge(root[x],root[t[i]],1,tot);
    if (!a[x].empty()) fo(i,0,a[x].size()-1) 
    change(root[x],1,tot,a[x][i],b[x][i]);
    ans[x]=tr[root[x]].id;
}
int main() {
    scanf("%d%d",&n,&m);
    fo(i,1,n-1) scanf("%d%d",&x,&y),
    add(x,y),add(y,x);dfs(1,0);
    fo(j,1,16) 
        fo(i,1,n) 
            fa[i][j]=fa[fa[i][j-1]][j-1];
    fo(i,1,m) {
        scanf("%d%d%d",&x,&y,&z);int rt=lca(x,y);
        if (!h[z]) h[z]=++tot,p[tot]=z;z=h[z];
        a[x].push_back(z);b[x].push_back(1);
        a[y].push_back(z);b[y].push_back(1);
        a[rt].push_back(z);b[rt].push_back(-1);
        a[fa[rt][0]].push_back(z);b[fa[rt][0]].push_back(-1);
    }
    make(1,0);
    fo(i,1,n) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值