P1600 [NOIP2016 提高组] 天天爱跑步 树上差分*

Link
参考文章
一道我觉得非常好的题目,有些地方还没想清楚,需要再回顾一下。
问题的第一个关键就在于把原问题转化:
m m m 个玩家,其中第 i i i 个玩家给 S i S_i Si 到 lca( S i , T i S_i, T_i Si,Ti)的路径上每个点增加一个类型为 d [ S i ] d[S_i] d[Si] 的物品,求每个点处类型为 w [ x ] + d [ x ] w[x] + d[x] w[x]+d[x] 的物品有多少个?(另一边类似)
典型的树上差分,但和上题不同,由于上题求的是物品数量最大值的类型,需要用到权值线段树&线段树合并,本题的性质满足区间减法的性质。所以这就是本题的第二个关键点,直接dfs即可。然而这个dfs还想了半天

const int maxn = 3e5 + 10;
const int maxm = 6e5 + 10;
const int P = 1e9 + 7;    //998244353
const int INF = 0x3f3f3f3f;
const double eps=1e-7;
int n, m;
int Log2[maxn], fa[maxn][30], dep[maxn];
bool vis[maxn];
int head[maxn];
int p;
struct Edge {
    int to, dis = 1, next;
}edge[maxm];
void dfs(int cur = 1, int fath = 0) {
    if(vis[cur]) return;
    vis[cur] = true;
    dep[cur] = dep[fath] + 1;
    fa[cur][0] = fath;
    for(int i = 1; i <= Log2[dep[cur]]; i++)
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    for(int i = head[cur]; i; i = edge[i].next)
        dfs(edge[i].to, cur);
}
int LCA(int a, int b) {
    if(dep[a] > dep[b])
        swap(a, b);
    while(dep[a] != dep[b])
        b = fa[b][Log2[dep[b]-dep[a]]];
    if(a == b)
        return a;
    for(int k = Log2[dep[a]]; k >= 0; k--)  //跳跃长度从长到短
        if(fa[a][k] != fa[b][k]) {
            a = fa[a][k];
            b = fa[b][k];
        }
    return fa[a][0];
}
void init() {
    for(int i = 1; i <= n; i++) {
        dep[i] = 0;
        head[i] = 0;
    }
    p = 0;
    for(int i = 2; i <= n; i++)
        Log2[i] = Log2[i / 2] + 1;
}
void add_edge(int u, int v, int w = 1) {
    p++;
    edge[p].to = v;
    edge[p].dis = w;
    edge[p].next = head[u];
    head[u] = p;
}
int w[maxn];
vector< pii > op1[maxn], op2[maxn];
int d1[maxn << 1], d2[maxn << 1];
int ans[maxn];
void update(int s, int t) {
    int p = LCA(s, t);
    op1[s].pb(make_pair(dep[s], 1));
    op1[fa[p][0]].pb(make_pair(dep[s], -1));
    op2[t].pb(make_pair(dep[s]-2*dep[p]+n, 1));
    op2[p].pb(make_pair(dep[s]-2*dep[p]+n, -1));
}
void dfs2(int u, int last) {
    int v1 = w[u] + dep[u];
    int v2 = w[u] - dep[u] + n;
    int res1 = d1[v1], res2 = d2[v2];
    for(int i = head[u]; i; i = edge[i].next) {
        int v = edge[i].to;
        if(v == last) continue;
        dfs2(v, u);
    }
    for(int i = 0; i < op1[u].size(); i++)
        d1[op1[u][i].first] += op1[u][i].second;
    for(int i = 0; i < op2[u].size(); i++)
        d2[op2[u][i].first] +=  op2[u][i].second;
    ans[u] = (d1[v1] - res1) + (d2[v2] - res2);
}
void solve() {
    cin >> n >> m;
    init();
    for(int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add_edge(u, v);
        add_edge(v, u);
    }
    dep[0] = -1;
    dfs();
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(int i = 1; i <= m; i++) {
        int s, t;
        cin >> s >> t;
        update(s, t);
    }
    dfs2(1, 0);
    for(int i = 1; i <= n; i++)
        cout << ans[i] << ' ';
    cout << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值