[NOIP2016]天天爱跑步

题目链接

虽然确实用 d f n 序计算贡献比递归调用好写也不饶,但谁让我是犟种呢 \sout{虽然确实用dfn序计算贡献比递归调用好写也不饶,但谁让我是犟种呢} 虽然确实用dfn序计算贡献比递归调用好写也不饶,但谁让我是犟种呢

题目大意就是说每个点 i i i有个观察员,会在 w i w_i wi时刻观察,有 m m m条路径,人沿路径跑,只有在 w i w_i wi那一时刻人刚好到那里才会让答案 + 1 +1 +1,问每个点的答案

思路:
也是学别人知道可以用 D S U   o n   T r e e DSU\ on\ Tree DSU on Tree做的,考虑对于一个点 x x x,哪些情况会对答案有贡献,必要条件显然得是,某一条路径的起点或者终点在以 x x x为根的子树里面,然后还有刚好在 w x w_x wx时刻跑到 x x x的限制.

我们分两种情况考虑:
1. 1. 1.起点在以 x x x为根的子树里面
2. 2. 2.终点在以 x x x为根的子树里面

对于情况一,可以看成是与 x x x深度差为 w x w_x wx的点
对于情况二,显然要满足 路径长度 − 深度差 = w x 路径长度-深度差 = w_x 路径长度深度差=wx
写成方程类似于 l e n − ( d e p y − d e p x ) = w x len-(dep_y-dep_x) = w_x len(depydepx)=wx
其中y是终点, l e n len len是路径长度

移项一下可以得到 l e n − d e p y = d e p x + w x len-dep_y=dep_x+w_x lendepy=depx+wx
左式和右式都只与自身有关.小trick来了,可以开两个桶解决,每次计算询问对应桶的值即可

一些细节:
路径会被计算两遍,如果是以x为起点和终点的 l c a lca lca的话,要去重,记录一下以x为 l c a lca lca的路径即可

最重要的是,以 x x x l c a lca lca的路径对上面的节点是没有贡献的,要删除它们

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;

const int maxn = 3e5+10;
const int base = 3e5+1;//偏移量,防止索引是负的
int n,m;
int w[maxn],ans[maxn],s[maxn],t[maxn],len[maxn];//len是路径长度
int f[maxn][25],sz[maxn],son[maxn],HH,l[maxn],dep[maxn];
int cnt0[maxn<<1],cnt1[maxn<<1];//桶,如果开map就可以不用偏移量了
vector<int>mark[maxn];//mark[i],以i为lca的路径
vector<int>g[maxn],ed[maxn],st[maxn];//树,以i为起点的路径st[i],以i为终点的路径ed[i]

void dfs(int x,int fa){
    sz[x]=1;
    f[x][0]=fa;
    dep[x] = dep[fa]+1;
    for(int i = 1;i<25;++i) f[x][i] = f[f[x][i-1]][i-1];
    for(auto y:g[x]){
        if(y==fa) continue;
        dfs(y,x);
        sz[x]+=sz[y];
        if(sz[y]>sz[son[x]]) son[x]=y;
    }
}

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


void calc(int x,int fa,bool op,int root){
    if(op){
        for(auto p:st[x]){
            if(dep[l[p]]<=dep[root]) ++cnt0[dep[x]];
        }
        for(auto p:ed[x]){
            if(dep[l[p]]<=dep[root]) ++cnt1[len[p]-dep[x]+base];
        }
    }else{
        for(auto p:st[x]){
            if(dep[l[p]]<=dep[root]) --cnt0[dep[x]];
        }
        for(auto p:ed[x]){
            if(dep[l[p]]<=dep[root]) --cnt1[len[p]-dep[x]+base];
        }
    }
    for(auto y:g[x]){
        if(y==fa||y==HH) continue;
        calc(y,x,op,root);
    }
}

void del(int x, bool flag) {
    for(auto p : mark[x]) {
        if(dep[s[p]] == dep[x] + w[x]) ans[x] --;//把重复计算的删掉
        if(flag) cnt0[dep[s[p]]] --, cnt1[len[p] - dep[t[p]] + base] --;//只有当x是重儿子时才删,因为若x是轻儿子在下面就会被清空了
    }
}


void dsu(int x,int fa,bool op){
    for(auto y:g[x]){
        if(y==fa||y==son[x]) continue;
        dsu(y,x,0);
    }
    if(son[x]) dsu(son[x],x,1),HH=son[x];
    calc(x,fa,1,x);
    HH=0;
    ans[x] += cnt0[dep[x] + w[x]] + cnt1[w[x] - dep[x] + base];
    del(x,op);//把lca为x的路径贡献删掉,因为它不会对上面有贡献
    if(!op) calc(x,fa,0,x);
}


signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i = 1;i<n;++i){
        int x,y;cin>>x>>y;
        g[x].emplace_back(y);
        g[y].emplace_back(x);
    }
    for(int i = 1;i<=n;++i) cin>>w[i];
    dfs(1,0);
    for(int i = 1;i<=m;++i){
        cin>>s[i]>>t[i];
        l[i] = lca(s[i],t[i]);//l[i]是lca
        len[i] = dep[s[i]]+dep[t[i]]-2*dep[l[i]];
        mark[l[i]].emplace_back(i);
        ed[t[i]].emplace_back(i);
        st[s[i]].emplace_back(i);
    }
    dsu(1,0,0);
    for(int i = 1;i<=n;++i) cout<<ans[i]<<" ";
    cout<<"\n";
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值