洛谷P1600 天天爱跑步

 树上的题有点意思啊

这题我最开始的想法是 固定一个观测点i,能观测到的一定是起点距离 i 为 w[ i ] 的点的子集合。问题是这个集合只有一部分的点会经过 i 点,就很烦。。。

于是得换个思路,不放固定一个跑步的人 x 观察他对哪些观察者 i 产生了贡献。

于是我们得到了公式,

对于上升阶段的点来说,不妨设 s 为起点  

d[x]+w[x] = d[s]( d为表示深度的数组 )

对于下降阶段的点来说,不妨设 t 为终点

dis[ s,t ] - d[t] = w[i] - deep[i]

这样,我们就把问题准换成了查询每个点的周围满足上述公式的点有多少个(注意,s和t 的lca处有可能会出现重复计算)

另外,上述表达式的值可能为负,所以用桶记录的时候要加上一个很大的值使其非负。

我们可以发现,对于每一个观察者 i 来说,经过他的路径的起点和终点 至少有一端 在 以他为根的子树中。于是我们将问题转化成 查询 在以 i 为根的子树中满足上述公式 且路径经过i点 的点有多少个。

那么我们重点解决的问题就是如何判断路径是否经过某个点。方法就是利用差分。对于每条路径,我们在起点和终点的lca处记录一下 起点和终点在这里的贡献,当从这个点回溯的时候,要把这个贡献从桶中去掉。

剩下最后一个问题就是这样做的话,计算 i 点时桶的内容会包含 其他子树的贡献。方式就是进入 i 点的时候我们先记录一下当时的贡献,从 i 点回溯的时候我们再记录一下此时桶中的内容,后者减去前者就是 以 i 为根节点的子树的贡献。 

#include <cstdio>
#include<cstring>
#include <queue>
using namespace std;
typedef long long LL;
typedef int lint;
typedef pair<lint,lint> pii;
const lint maxn = 300000;
const lint maxm = 600000;
lint ver[maxm],ne[maxm],he[maxn],tot;
void init(){
    memset( he,0,sizeof(he) );
    tot = 1;
}
void add( lint x,lint y ){
    ver[++tot]  = y;
    ne[tot] = he[x];
    he[x] = tot;
}
queue<lint> que;
lint d[maxn],f[maxn][21];
void build(){
    que.push(1);
    d[1] = 0;
    while( que.size() ){
        lint x = que.front();
        que.pop();
        for( lint cure = he[x];cure;cure = ne[cure] ){
            lint y = ver[cure];
            if( y == f[x][0] ) continue;
            que.push(y);
            d[y] = d[x] + 1;
            f[y][0] = x;
            for( lint i = 1;(1<<i) <= d[y];i++ ){
                f[y][i] = f[ f[y][i-1] ][i-1];
            }
        }
    }
}
lint lca( lint x,lint y ){
    if( d[x] < d[y] ) swap( x,y );
    for( lint i = 20;i >= 0;i-- ){
        if( f[x][i] && d[ f[x][i] ] >= d[y] ) x =f[x][i];
    }
    if( x == y ) return x;
    for( lint  i = 20;i >= 0;i-- ){
        if( f[x][i] == f[y][i] ) continue;
        x = f[x][i];
        y = f[y][i];
    }
    return f[x][0];
}
lint w[maxn],ans[maxn];
vector<lint> iss[maxn];
vector<pii> ist[maxn];
lint tongs[5*maxn],tongt[5*maxn];
vector<lint> ves[maxn],vet[maxn];
lint buckets[maxn],buckett[maxn];
void dfs( lint x,lint f ){
    buckets[x] = tongs[ d[x]+w[x]+maxn ];
    buckett[x] = tongt[ w[x]-d[x]+maxn ];
    for( lint cure = he[x];cure;cure = ne[cure] ){
        lint y = ver[cure];
        if( y == f ) continue;
        dfs(y,x);
    }
    for( lint i = 0;i < iss[x].size();i++ ){
        lint t = iss[x][i];
        tongs[ d[x]+maxn ]++;
        ves[t].push_back( d[x]+maxn );
    }
    for( lint i = 0;i < ist[x].size();i++ ){
        lint s = ist[x][i].first;
        lint c = ist[x][i].second;
        lint dist = d[x] + d[s] - 2*d[c];
        tongt[ dist - d[x]+maxn  ]++;
        vet[c].push_back( dist-d[x]+maxn );
    }
    ans[x] += tongs[ d[x]+w[x]+maxn ] - buckets[x];
    ans[x] += tongt[ w[x] - d[x]+maxn ] - buckett[x];
    for( lint i = 0;i < ves[x].size();i++ ){
        tongs[ ves[x][i] ]--;
    }
    for( lint i = 0;i < vet[x].size();i++ ){
        tongt[ vet[x][i] ]--;
    }
}
int main(){
    lint n,m;
    scanf("%d%d",&n,&m);
    for( lint x,y,i = 1;i <= n-1;i++ ){
        scanf("%d%d",&x,&y);
        add( x,y );add( y,x );
    }
    build();
    for( lint i = 1;i <= n;i++ ) scanf("%d",&w[i]);
    for( lint x,y,i = 1;i <= m;i++ ){
        scanf("%d%d",&x,&y);
        lint z = lca( x,y );
        if( d[z] + w[z] == d[x] )
        ans[z]--;
        iss[x].push_back( z );
        ist[y].push_back( pii( x,z ) );
    }
    dfs(1,0);
    for( lint 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、付费专栏及课程。

余额充值