【UOJ261 BZOJ 】天天爱跑步(线段树合并)

传送门

天天爱跑步

I Think

    题意:给出一颗n个节点的树,每个节点i上有一个仅在时刻 wi 进行观察的观察员。有m个人在树上跑步,他们同时在0时刻开始从起点 Si 跑向 Ti ,每秒钟跑过1条边。观察员i观察到第j个人,当且仅当第j个人在第 wi 秒时恰好到达了i节点。每个人到达终点的下一秒即消失。问每个观察员能够观察到多少个人。
    算法:线段树合并
    思路:考虑一条路径 (S,T) 对节点u可能产生的贡献:
    1)当S在u的子树内时,若 deep[S]deep[u]=w[u] 时,该路径对点u有贡献1,移项后:

deep[u]+w[u]=deep[S]

    2)当T在u的子树内时,若 Lca=LCA(S,T) , deep[S]deep[Lca]+deep[u]deep[Lca]=w[u] ,该路径对点u有贡献1,移项后:
deep[u]w[u]=2×deep[Lca]deep[S]

    那么计算每个点的答案,则可以建立两颗权值线段树,在树上进行DFS时,自底向上分别在对应权值线段树中插入每个起/终点的贡献 deep[S] , 2×deep[Lca]deep[S] 。一边向上走更新答案,一边合并子树的权值线段树。
    3)当S,T均在u的子树内时,若 u=Lca 则路径S,T才可能产生一次贡献。但是根据操作1),2),此时该路径的贡献会被计算两次,因此要删除一次以点u为LCA的路径贡献,再统计答案。统计答案之后,该路径也不再会对u的任意祖先节点产生贡献,因此删去它在权值线段树中的另一次贡献。
    实现时可用vector保存以某点为T/LCA的路径。
    注意2)操作的权值可能为负,所以左右二式均加上N+1即可。

Code

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

const int sm = 3e5+5;
const int sn = 2400000;

int N,M,tot,lim;
int Rt[2][sm],cnt[2][sn],Ls[2][sn],Rs[2][sn],Lc[sm];
int w[sm],S[sm],T[sm],tp,stk[sn],sum[sm],Ans[sm];//bzoj数据较强需要把stk开到比较大 否则WA
//sum[i]记从i点出发的路径条数
int Fa[sm][22],dep[sm],hd[sm],to[sm<<1],nxt[sm<<1];

vector<int > t[sm],lc[sm];
//t[i]记在i点结束的路径编号 lc[i]记以i为lca的路径编号

template <typename T> void read(T &x) {
    char ch=getchar();x=0;
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Swap(int &x,int &y) { int t=x;x=y;y=t; }
void Ins(int x,int y) {
    to[++tot]=y,nxt[tot]=hd[x],hd[x]=tot;
    to[++tot]=x,nxt[tot]=hd[y],hd[y]=tot;
}
void Dfsa(int x,int fa) {
    Fa[x][0]=fa,dep[x]=dep[fa]+1;
    for(int i=1;i<=19;++i)
        if(Fa[x][i-1]) Fa[x][i]=Fa[Fa[x][i-1]][i-1];
        else break;
    for(int i=hd[x];i;i=nxt[i])
        if(to[i]!=fa) Dfsa(to[i],x);
}
int Lca(int u,int v) {
    if(dep[u]!=dep[v]) {
        if(dep[u]<dep[v]) Swap(u,v);
        for(int i=19;i>=0;--i)
            if(dep[Fa[u][i]]>dep[v])
                u=Fa[u][i];
        u=Fa[u][0];
    }
    if(u==v)return v;
    for(int i=19;i>=0;--i) 
        if(Fa[u][i]!=Fa[v][i])
            u=Fa[u][i],v=Fa[v][i];
    return Fa[u][0];
}
void del(int &x,int k) {
    stk[++tp]=x,Ls[k][x]=Rs[k][x]=cnt[k][x]=0,x=0;
}
int Merge(int x,int &y,int k) {
    if(!(x*y))return x+y;
    cnt[k][x]+=cnt[k][y];
    Ls[k][x]=Merge(Ls[k][x],Ls[k][y],k);
    Rs[k][x]=Merge(Rs[k][x],Rs[k][y],k);
    del(y,k);
    return x;
}
void Add(int &rt,int l,int r,int p,int val,int k) {
    if(!rt) { rt = tp? stk[tp--] : ++tot;}
    cnt[k][rt]+=val;
    if(l==r) { if(!cnt[k][rt])del(rt,k); return; }
    int m=(l+r)>>1;
    if(p<=m) Add(Ls[k][rt],l,m,p,val,k);
    else Add(Rs[k][rt],m+1,r,p,val,k);
    if(!Ls[k][rt]&&!Rs[k][rt])del(rt,k);
}
int Query(int rt,int l,int r,int p,int k) {
    if(!rt)return 0;
    if(l==r) return cnt[k][rt];
    int m=(l+r)>>1;
    if(p<=m) return Query(Ls[k][rt],l,m,p,k);
    else return Query(Rs[k][rt],m+1,r,p,k);
}
void Dfsb(int x,int fa) {
    for(int i=hd[x];i;i=nxt[i]) {
        if(to[i]!=fa) Dfsb(to[i],x); else continue;
        Rt[0][x]=Merge(Rt[0][x],Rt[0][to[i]],0);
        Rt[1][x]=Merge(Rt[1][x],Rt[1][to[i]],1);
    }
    if(sum[x]) Add(Rt[0][x],1,lim,dep[x],sum[x],0);
    for(int i=0,p;i<t[x].size();++i) {
        p=N+1+dep[S[t[x][i]]]-(dep[Lc[t[x][i]]]<<1); 
        Add(Rt[1][x],1,lim,p,1,1);
    }
    Ans[x]+=Query(Rt[0][x],1,lim,w[x]+dep[x],0);
    for(int i=0,p;i<lc[x].size();++i) { //删除以点x为Lca的路径
        Add(Rt[0][x],1,lim,dep[S[lc[x][i]]],-1,0);
        Add(Rt[1][x],1,lim,dep[S[lc[x][i]]]-(dep[x]<<1)+N+1,-1,1);
    }
    Ans[x]+=Query(Rt[1][x],1,lim,w[x]-dep[x]+N+1,1);
}
int main() {
    read(N),read(M),lim=N<<1|1;
    for(int i=1,u,v;i<N;++i) 
        read(u),read(v),Ins(u,v);
    for(int i=1;i<=N;++i) read(w[i]);
    for(int i=1;i<=M;++i) {
        read(S[i]),read(T[i]);
        sum[S[i]]++,t[T[i]].push_back(i);
    }
    tot=0,Dfsa(1,0);
    for(int i=1;i<=M;++i)
        lc[Lc[i]=Lca(S[i],T[i])].push_back(i);
    Dfsb(1,0);
    printf("%d",Ans[1]);
    for(int i=2;i<=N;++i)
        printf(" %d",Ans[i]);
    return 0;
}

更新一波两颗线段树节点不需区分的代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int sn = 3e5 + 19;
const int sm = 35e6 + 19;

int n,m,lim,tot;
int w[sn],ans[sn];
int to[sn<<1],nxt[sn<<1],hd[sn],dep[sn];
int f[sn][20],S[sn],T[sn],lca[sn],st[sn];

vector<int> tt[sn],lc[sn];

void Swap(int &x,int &y) { int t = x; x = y; y = t; }
void Add(int u,int v) { 
    to[++tot] = v, nxt[tot] = hd[u], hd[u] = tot;
    to[++tot] = u, nxt[tot] = hd[v], hd[v] = tot;
}

#define mid ((l+r)>>1)
namespace St {
    int Rt[2][sn],cnt;
    int val[sm],Ls[sm],Rs[sm];

    void Ins(int &rt,int l,int r,int pos,int k) {
        if(!rt) rt = ++cnt;
        val[rt] += k;
        if(l == r) return;
        if(pos <= mid) Ins(Ls[rt],l,mid,pos,k);
        else Ins(Rs[rt],mid+1,r,pos,k);
    }

    int Query(int rt,int l,int r,int pos) {
        if(!rt) return 0;
        if(l == r) return val[rt];
        if(pos <= mid) return Query(Ls[rt],l,mid,pos);
        else return Query(Rs[rt],mid+1,r,pos); 
    }

    int Merge(int Rt,int rt) {
        if(!(Rt * rt)) return Rt + rt;
        val[Rt] += val[rt];
        Ls[Rt] = Merge(Ls[Rt],Ls[rt]);
        Rs[Rt] = Merge(Rs[Rt],Rs[rt]);
        return Rt;
    }
}
using namespace St;

void Dfs1(int x,int fa) {
    f[x][0] = fa, dep[x] = dep[fa]+1;
    for(int i = 1; i <= 19; ++i)
        if(f[x][i-1]) f[x][i] = f[f[x][i-1]][i-1];
        else break;
    for(int i = hd[x]; i; i = nxt[i])
        if(to[i]!=fa) Dfs1(to[i],x);
}

int Lca(int u,int v) {
    if(dep[u] < dep[v]) Swap(u,v);
    for(int i = 19; i >= 0; --i)
        if(dep[f[u][i]] >= dep[v]) 
            u = f[u][i];
    if(u == v) return v;
    for(int i = 19; i >= 0; --i)
        if(f[u][i] != f[v][i])
            u = f[u][i], v = f[v][i];
    return f[u][0];
}

void Dfs2(int x) {
    for(int i = hd[x]; i; i = nxt[i])
        if(to[i] != f[x][0]) {
            Dfs2(to[i]);
            Rt[0][x] = Merge(Rt[0][x],Rt[0][to[i]]);
            Rt[1][x] = Merge(Rt[1][x],Rt[1][to[i]]);
        }

    if(st[x]) Ins(Rt[0][x],1,lim,dep[x],st[x]); 

    int loc;
    for(int i = 0,num; i < tt[x].size(); ++i) {
        num = tt[x][i];
        loc = dep[S[num]]-(dep[lca[num]]<<1)+n+1;
        Ins(Rt[1][x],1,lim,loc,1);
    }

    ans[x] = Query(Rt[1][x],1,lim,w[x]-dep[x]+n+1);

    for(int i = 0,num; i < lc[x].size(); ++i) {
        num = lc[x][i];
        loc = dep[S[num]]-(dep[x]<<1)+n+1;
        Ins(Rt[1][x],1,lim,loc,-1);
        Ins(Rt[0][x],1,lim,dep[S[num]],-1);
    }

    ans[x] += Query(Rt[0][x],1,lim,dep[x]+w[x]);
}

int main() {
    int u,v;

    scanf("%d%d",&n,&m);
    for(int i = 1; i < n; ++i)
        scanf("%d%d",&u,&v), Add(u,v);
    for(int i = 1; i <= n; ++i)
        scanf("%d",&w[i]);
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d",&S[i],&T[i]);
        ++st[S[i]];
        tt[T[i]].push_back(i);
    }

    Dfs1(1,0);
    for(int i = 1; i <= m; ++i)
        lc[lca[i] = Lca(S[i],T[i])].push_back(i);
    lim = n<<1|1, Dfs2(1);

    for(int i = 1; i <= n; ++i)
        printf("%d ",ans[i]);
    putchar(10);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值