bzoj 3307 雨天的尾巴(树上差分,动态开点线段树,线段树的合并)

题目链接:AcWing 353

题意:一颗树,m次操作,x,y,z,每次给x到y的链上的所有点都增加一个种类为z的物品,问最后每个点上最多的物品是什么,若有多个物品相同,则输出编号最小的。
数据范围: n , m &lt; = 1 0 5 , 1 &lt; = z &lt; = 1 0 9 n,m&lt;=10^5,1&lt;=z&lt;=10^9 n,m<=1051<=z<=109

这里直接给出一个普适的方法。
给链x->y的点加一个数字z。
一般是在x,y处+z,在lca(x,y)处-z,在father(lca)处-z。

对于边的差分,用一条边的儿子表示这条边,那么考虑经过x—>y的边的次数,差分表示为 x,y处+1,lca(x,y)处—2即可,在边的差分里,树的根节点是无意义的存在。

那么一次操作就可以拆为4次单点加减,先考虑一个MLE的做法,也即对于每个点都开一个权值线段树来记录该点上出现的数字最多的是谁。
为了避免MLE,就需要用线段树的动态开点了,最开始我们建了一颗空的线段树,每次需要用到什么点,我们就为他动态的开一个点出来。
做完所有点操作后,一次dfs,统计答案就需要将一个点与他的子树中点的线段树合并起来。就像树状数组那种差分的形式,要询问一个点的权值,是不是需要将覆盖过他的修改都加起来,这里线段树的合并起的作用与之一致。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+7;

int b[maxn],m;
void quchong(int n){
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}
int head[maxn],top;
struct Edge{
    int v,next;
}edge[maxn<<1];

void init(){
    top=0;
    memset(head,-1,sizeof(head));
}

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}
int tot;
struct Tree{
    int lc,rc;
    int maxx,id;
}a[maxn*50];

int root[maxn];
//动态开点;
int build(){
    int k=++tot;
    a[k].lc=a[k].rc=0;
    a[k].maxx=a[k].id=0;
    return k;
}
//更新;
void insert(int p,int l,int r,int id,int val){
    if(l==r){
        a[p].maxx+=val;
        if(a[p].maxx==0) a[p].id=0;
        else a[p].id=l;
        return ;
    }
    int mid=(l+r)>>1;
    if(id<=mid){
        if(!a[p].lc) a[p].lc=build();
        insert(a[p].lc,l,mid,id,val);
    }
    else{
        if(!a[p].rc) a[p].rc=build();
        insert(a[p].rc,mid+1,r,id,val);
    }
   // 当有多个最大值出现时,需要选取编号更小的,所以这里是>=;
    if(a[a[p].lc].maxx>=a[a[p].rc].maxx) a[p].maxx=a[a[p].lc].maxx,a[p].id=a[a[p].lc].id;
    else a[p].maxx=a[a[p].rc].maxx,a[p].id=a[a[p].rc].id;
}
//将根节点为q的线段树的信息合并到p上;
int marge(int p,int q,int l,int r){
    if(!p) return q;
    if(!q) return p;
    if(l==r){
        a[p].maxx+=a[q].maxx;
        if(a[p].maxx) a[p].id=l;
        else a[p].id=0;
        return p;
    }
    int mid=(l+r)>>1;
    a[p].lc=marge(a[p].lc,a[q].lc,l,mid);
    a[p].rc=marge(a[p].rc,a[q].rc,mid+1,r);
    if(a[a[p].lc].maxx>=a[a[p].rc].maxx) a[p].maxx=a[a[p].lc].maxx,a[p].id=a[a[p].lc].id;
    else a[p].maxx=a[a[p].rc].maxx,a[p].id=a[a[p].rc].id;
    return p;
}

int fa[maxn][29];
const int ci=20;
queue<int> q;
int dep[maxn];
void bfs(int st){
    q.push(st);
    dep[st]=1;
    int u,v;
    while(!q.empty()){
        u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=edge[i].next){
            v=edge[i].v;
            if(dep[v]) continue;
            dep[v]=dep[u]+1;
            fa[v][0]=u;
            for(int j=1;j<=ci;++j)
                fa[v][j]=fa[fa[v][j-1]][j-1];
            q.push(v);
        }
    }
}

int LCA(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=ci;i>=0;--i)
        if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=ci;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

void updata(int x,int y,int z){
    int w=LCA(x,y);
    z=getid(z);
    insert(root[x],1,m,z,1);
    insert(root[y],1,m,z,1);
    insert(root[w],1,m,z,-1);
    if(fa[w][0]) insert(root[fa[w][0]],1,m,z,-1);
}
bool vis[maxn];
int ans[maxn];
void dfs(int u){//统计答案;
    int v;
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(vis[v]) continue;
        dfs(v);
        root[u]=marge(root[u],root[v],1,m);
    }
    ans[u]=b[a[root[u]].id];
}
struct Node{
    int l,r,w;
}c[maxn];
int main(){
    int n,q,u,v,w;
    init();
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    tot=0;
    for(int i=1;i<=n;++i) root[i]=build();
    bfs(1);
    for(int i=1;i<=q;++i){
        scanf("%d%d%d",&c[i].l,&c[i].r,&c[i].w);
        b[i]=c[i].w;
    }
    quchong(q);
    for(int i=1;i<=q;++i) updata(c[i].l,c[i].r,c[i].w);
    dfs(1);
    for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值