洛谷P1600 树上路径

题意:

nnn个地方,被n−1n-1n1条道路连接,有mmm个人同时开始跑,起点和终点分别为ui,viu_{i},v_{i}ui,vi,每个地方有一个观察员,仅会在tit_{i}ti时刻观察到达iii地方的人,问每个观察员能看到多少个人?

Solution:

显然模拟跑步过程复杂度不够,每次模拟路程长度≤n\leq nn,复杂度O(nm)O(nm)O(nm)。换个角度,由于每个跑步者的路径(x→y)(x\rightarrow y)(xy)一定经过lca(x,y)lca(x,y)lca(x,y),她只会给lca(x,y)lca(x,y)lca(x,y)这棵子树的观察员带来贡献,由于这是个单向过程,我们考虑拆分路径为(x→lca→y)(x\rightarrow lca\rightarrow y)(xlcay),不妨说分为上行和下行两段。

先考虑上行能给uuu点带来贡献的点有什么条件,显然,从起点出发,到达的时候恰好被看到,即花费了tut_{u}tu到达uuu,又由于是上行,于是只要深度为depth[u]+t[u]depth[u]+t[u]depth[u]+t[u]的点,并且位于uuu的子树内,就是被看到的上行点。

再考虑下行能给uuu点带来贡献的点有什么条件,同理,从起点出发tut_{u}tu秒被看到,那么他们的总路径长就是(x→u→y)(x\rightarrow u \rightarrow y)(xuy),有如下等式

dis(x,y)=dis(x,u)+dis(u,y) dis(x,y)=dis(x,u)+dis(u,y) dis(x,y)=dis(x,u)+dis(u,y)

其中dis(x,u)dis(x,u)dis(x,u)恰好是时间tut_{u}tu,而由于下行,u,yu,yu,y位于同一条链,于是

dis(x,y)=tu+depthy−depthu dis(x,y)=t_{u}+depth_{y}-depth_{u} dis(x,y)=tu+depthydepthu

depthy−dis(x,y)=depthu−tu depth_{y}-dis(x,y)=depth_{u}-t_{u} depthydis(x,y)=depthutu

终点yyy满足以上等式的即是下行给uuu带来贡献的点。

两部分答案都可以用桶解决,需要注意的是

(1)由于答案限制在子树内,我们需要获得单一子树的桶,不能暴力统计,需要启发式合并

(2)由于答案限制在子树内,需要提前统计以uuulcalcalca的点,在结束uuu的递归的时候把以uuulcalcalca的点的贡献全删了

(3)第二部分的等式一边可能是负,于是需要偏移数组

(4)两部分答案的计算顺序不同,第一部分顺序为:加上当前点的贡献,计算答案,减去以该点为lcalcalca的贡献,第二部分由于第一部分已经统计过以uuu点为lcalcalca的路径,所以顺序为:加上当前点的贡献,减去以该点为lcalcalca的贡献,计算答案。

// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;

using ll=long long;
const int N=300005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;

struct node
{
    int first,second;
}a[N];

struct way
{
    int to,next;
}edge[N<<1];
int cntt,head[N];

void add(int u,int v)
{
    edge[++cntt].to=v;
    edge[cntt].next=head[u];
    head[u]=cntt;
}

int n,m,t[N],tot[N],f[N][21],depth[N],cnt[N<<2];
int ans[N],val[N],sum[N],son[N];
vector<vector<int>>from(N),lcaof(N);

void dfs1(int u,int fa)
{
    // printf("u=%d\n",u);
    f[u][0]=fa; depth[u]=depth[fa]+(sum[u]=1);
    for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs1(v,u);
        sum[u]+=sum[v];
        if(sum[v]>sum[son[u]]) son[u]=v;
    }
}

int lca(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y);
    for(int i=20;i>=0;i--)
        if(depth[x]-(1<<i)>=depth[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}

int dis(int x,int y)
{
    int tmp=lca(x,y);
    return depth[x]+depth[y]-2*depth[tmp];
}

void gls2(int u,int hson,int fa,int k)
{
    cnt[depth[u]]+=val[u]*k;
    for(auto v:lcaof[u]) cnt[depth[v]]-=k;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if((k==1&&v==hson)||v==fa) continue;
        gls2(v,hson,u,k);
    }
}

void dfs2(int u,int fa)
{
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa||v==son[u]) continue;
        dfs2(v,u);
    }
    if(son[u]) dfs2(son[u],u);
    gls2(u,son[u],fa,1);
    ans[u]=cnt[depth[u]+t[u]]+tot[u];
    if(son[fa]!=u) gls2(u,son[u],fa,-1);
}

void gls3(int u,int hson,int fa,int k)
{
    for(auto v:lcaof[u]) cnt[depth[u]-dis(u,v)+N]-=k;
    for(auto v:from[u]) cnt[depth[u]-dis(u,v)+N]+=k;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if((k==1&&v==hson)||v==fa) continue;
        gls3(v,hson,u,k);
    }
}

void dfs3(int u,int fa)
{
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa||v==son[u]) continue;
        dfs3(v,u);
    }
    if(son[u]) dfs3(son[u],u);
    gls3(u,son[u],fa,1);
    ans[u]+=cnt[depth[u]-t[u]+N];
    if(son[fa]!=u) gls3(u,son[u],fa,-1);
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v; scanf("%d%d",&u,&v);
        add(u,v); add(v,u);
    }
    for(int i=1;i<=n;i++) scanf("%d",&t[i]);
    for(int i=1;i<=m;i++)
    {
        auto& [u,v]=a[i];
        scanf("%d%d",&u,&v);
        from[v].push_back(u);
    }
    dfs1(1,0);
    for(int i=1;i<=m;i++)
    {
        auto& [u,v]=a[i];
        int tmp=lca(u,v);
        if(depth[u]==depth[tmp]+t[tmp]) tot[tmp]++;//记录有多少个路线以u为lca,并且对这个点产生贡献
        //gls()统计完轻儿子时实际上已经清空了以这棵子树的点为lca的清空了,此时以u的需要手动求一下
        lcaof[tmp].push_back(u);//记录以tmp为lca的点
        val[u]++;//保存有多少个人以u为起点
    }
    dfs2(1,0);
    for(int i=1;i<=n;i++) lcaof[i].clear();
    memset(tot,0,sizeof(tot));
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=m;i++)
    {
        auto& [u,v]=a[i];
        lcaof[lca(u,v)].push_back(u);
    }
    dfs3(1,0);
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值