BZOJ3307 雨天的尾巴 (树链剖分 线段树合并 dfs相关)

题目大意

N个点,形成一个树状结构。有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品。完成所有发放后,每个点存放最多的是哪种物品。


题解

看到这道题之后我本能的想法就是位置线段树套权值线段树的,但是看了一下,这道题的时间限制和空间限制卡得都比较紧,而且是离线问题,树套树卡起来应该是十分的困难的。

这道题虽然是处理树上问题,但是我们先考虑如何处理这样的区间问题。 如果所有的操作和询问都是在区间上进行的话,如在[x,y]区间内各放置一个z物品,就在x位置打上z物品+1的标记,在y+1位置打上z物品-1的标记。从左到右扫、处理标记,用一棵权值线段树维护。

从区间问题转化到了树上问题,通过这道题我学到了一种新的手段。

每次在一个树上路径上进行修改的时候,如修改x到y的路径的时候,只需要在x和y各打一个z物品+1的标记,在lca和fa[lca]打上z物品-1的标记,这样在dfs递归求解、向上合并的时候就可以做到不重不漏了。

因为在x和y打一个标记,这个标记的作用范围分别是x和y到根的路径(因为被这个标记修改后的线段树会一路被合并到根节点),但是从lca到根的路径上是被这个标记作用了两次的。所以给lca打上z物品-1的标记之后,lca到根的路径这个标记就只作用一次了;再给fa[lca]打上z物品-1的标记,这样这个标记就恰好只覆盖从x到y的路径了。

所以大概的思路就是:

  1. 处理出各个结点的标记
  2. 自下向上合并线段树
  3. 处理标记,修改线段树
  4. 把答案记录下来

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=int(1e5)+111, inf=int(1e9)+7;
int n,m;
int Tot=0,head[maxn];

struct Edge {
    int to,next;
    Edge() {}
    Edge(int y,int nx):to(y),next(nx) {}
}eage[maxn*2];
inline void add(int x,int y) {
    eage[Tot]=Edge(y,head[x]), head[x]=Tot++;
    eage[Tot]=Edge(x,head[y]), head[y]=Tot++;
    return;
}

int fa[maxn],siz[maxn],dep[maxn],son[maxn];
void dfs1(register int u) {
    siz[u]=1, son[u]=0;
    for(register int i=head[u];~i;i=eage[i].next) if(eage[i].to!=fa[u]) {
        fa[eage[i].to]=u;
        dep[eage[i].to]=dep[u]+1;
        dfs1(eage[i].to);
        siz[u]+=siz[eage[i].to];
        if(siz[eage[i].to]>siz[son[u]]) son[u]=eage[i].to;
    }
    return;
}

int top[maxn],seq[maxn],id[maxn],ind;
void dfs2(register int u) {
    seq[id[u]=++ind]=u;
    if(u==son[fa[u]]) top[u]=top[fa[u]];
    else top[u]=u;
    if(son[u]) dfs2(son[u]);
    for(register int i=head[u];~i;i=eage[i].next) if(eage[i].to!=fa[u] && eage[i].to!=son[u])
        dfs2(eage[i].to);
    return;
}

inline int LCA(int u,int v) {
    while(top[u]!=top[v]) {
        if(dep[top[u]]<dep[top[v]]) std::swap(u,v);
        u=fa[top[u]];
    }
    return dep[u]<dep[v]?u:v;
}

vector<pair<int,int> > op[maxn];
#define pb push_back
#define mp make_pair
int v[maxn],vtop;

struct Oper {
    int x,y,z;
}q[maxn];

struct Node {
    int ls,rs;
    pair<int,int> pos;
    inline Node() {
        ls=rs=0;
        pos=mp(0,0);
    }
}node[int(2e6)];
int root[maxn],tot=0;
int ans[maxn];

inline void seg_merge(int k1,int k2,int l,int r) {
    if(l==r) {
        node[k1].pos.first+=node[k2].pos.first;
        return;
    }
    register int &ls=node[k1].ls, &rs=node[k1].rs, mid=(l+r)>>1;
    if(node[k2].ls) {
        if(!ls) ls=node[k2].ls;
        else seg_merge(ls,node[k2].ls,l,mid);
    } if(node[k2].rs) {
        if(!rs) rs=node[k2].rs;
        else seg_merge(rs,node[k2].rs,mid+1,r);
    }
    if(ls) node[k1].pos=max(node[k1].pos,node[ls].pos);
    if(rs) node[k1].pos=max(node[k1].pos,node[rs].pos);
    return;
}

inline void seg_modify(int k,int l,int r,int pos,int val) {
    if(l==r) {
        node[k].pos.first+=val, node[k].pos.second=-pos;
        return;
    }
    register int &ls=node[k].ls, &rs=node[k].rs, mid=(l+r)>>1;
    if(pos<=mid) {
        if(!ls) ls=++tot;
        seg_modify(ls,l,mid,pos,val);
        if(!node[ls].pos.first) ls=0;
    } else {
        if(!rs) rs=++tot;
        seg_modify(rs,mid+1,r,pos,val);
        if(!node[rs].pos.first) rs=0;
    }
    if(ls && rs) node[k].pos=max(node[ls].pos,node[rs].pos);
    else if(ls) node[k].pos=node[ls].pos;
    else if(rs) node[k].pos=node[rs].pos;
    else node[k].pos=mp(0,0);
    return;
}

inline void dfs(register int u) {
    root[u]=++tot;
    for(register int i=head[u];~i;i=eage[i].next) if(eage[i].to!=fa[u]) {
        dfs(eage[i].to);
        seg_merge(root[u],root[eage[i].to],1,vtop);
    }
    for(register int i=0;i<(int)op[u].size();i++) {
        seg_modify(root[u],1,vtop,op[u][i].first,op[u][i].second);
    }
    ans[u]=-node[root[u]].pos.second;
    return;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input0.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif // ONLINE_JUDGE
    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++)
        head[i]=-1;
    for(int x,y,i=1;i<n;i++) {
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    dfs1(1),dfs2(1);

    for(int i=1;i<=m;i++) {
        scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].z);
        v[++vtop]=q[i].z;
    }
    sort(v+1,v+1+vtop);
    vtop=unique(v+1,v+1+vtop)-(v+1);

    for(int i=1;i<=m;i++) {
        int x=q[i].x, y=q[i].y, z=lower_bound(v+1,v+1+vtop,q[i].z)-v;
        op[x].pb(mp(z,+1)), op[y].pb(mp(z,+1));
        int lca=LCA(x,y);
        op[lca].pb(mp(z,-1)), op[fa[lca]].pb(mp(z,-1));
    }

    dfs(1);
    for(int i=1;i<=n;i++) printf("%d\n",v[ans[i]]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值