CF575B Bribes(树上差分)

本文探讨了树链覆盖问题,介绍了一种基于树上差分的算法实现。通过树剖、LCA查询及快速幂运算,解决了点在树上移动时对边的贡献计算问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

思路 :

看到题目中说有两种边,之后又给出多个点跑来跑去,想到的是树链上的问题。但是由于树链上的点都会影响这条链对答案的贡献,我们可以选择思考是否可以计算一个点或者是一条边对于答案的贡献。想到每个点经过多少次,是一个树链覆盖,想到树上差分。

个人理解这里的差分是有向的,diff[0]代表从该点出发的覆盖, diff[1]代表从该点进入的覆盖,但是树上差分都是表示一个点到根节点(最后统计前缀和的这种顺序), 所以需要两个数组。
代码:

#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define ull unsigned long long
#define PI acos(-1)
#define pb(x)   push_back(x)
#define il inline
#define re register
#define IO; ios::sync_with_stdio(0);cin.tie(0);
//#define ls (o<<1)
//#define rs (o<<1|1)
#define pii pair<int,int>
using namespace std;
const int maxn = 1e6+5;
const int maxm = 10001000;
const int INF = 0x3f3f3f3f;
const ll LINF = 3e17+1;
const ll mod = 1e9+7;
const int MOD = 1e6+7;
inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}
struct edge
{
    int u,v,nxt;
    bool f;
}E[maxn<<1];
//	链式前向星存图
//	由于这里之后要遍历边 u到v 所以存下每个边的u
int n, m, head[maxn], eid, a[maxn], diff[2][maxn];
int fa[maxn], son[maxn], siz[maxn], dep[maxn], top[maxn];
void add(int u, int v, bool f)
{
    E[eid] = {u,v,head[u],f};
    head[u] = eid++;
}
//  树剖的dfs1 dfs2 准备lca
void dfs1(int u , int f, int deep)
{
    fa[u] = f;
    dep[u] = deep;
    siz[u] = 1;
    int maxson = -1;
    for (int i = head[u]; i != -1; i = E[i].nxt)
    {
        int v = E[i].v;
        if (v == f) continue ;
        dfs1(v,u,deep+1);
        siz[u] += siz[v];
        if (siz[v] > maxson) maxson = siz[v], son[u] = v;
    }
}
void dfs2(int u, int tp)
{
    top[u] = tp;
    if (!son[u]) return ;
    dfs2(son[u], tp);
    for (int i = head[u]; i != -1; i = E[i].nxt)
    {
        int v = E[i].v;
        if (v == son[u] || v == fa[u])  continue ;
        dfs2(v,v);
    }
}
// 	统计树上前缀和
void solve(int u, int f)
{
    for (int i = head[u]; i != -1; i = E[i].nxt)
    {
        int v = E[i].v;
        if (v == f) continue ;
        solve(v,u);
        diff[0][u] += diff[0][v];
        diff[1][u] += diff[1][v];
    }
}
int LCA(int x, int y)
{
    while (top[x] != top[y])
    {
        if (dep[top[x]] < dep[top[y]])    swap(x,y);
        x = fa[top[x]];
    }
    if (dep[x] > dep[y])    swap(x,y);
    return x;
}
//	快速幂等比数列求和算各边贡献
int qpow(int a, int b)
{
    int res = 1;
    for (; b; b >>= 1)
    {
        if (b&1)    res = 1ll*res*a%mod;
        a = 1ll*a*a%mod;
    }
    return res;
}
int main()
{
    n = read();
    int u, v;bool f;
    for (int i = 0; i <= n; i++)    head[i] = -1;
    for (int i = 1; i < n; i++)
    {
        u = read(), v = read(), f = read();
        add(u,v,f);add(v,u,f);
    }
    dfs1(1,0,1);dfs2(1,1);
    int k = read();int s = 1, t;
    for (int i = 1; i <= k; i++)
    {
        t = read();
        int lca = LCA(s, t);
        //  这里是树上差分的权值在边的情况,边权值赋给深度较深的点
        //	(还有一种权值在点的情况)
        diff[0][s]++;diff[0][lca]--;
        diff[1][t]++;diff[1][lca]--;
        s = t;
    }
    solve(1,0);
    ll ans = 0;
    //	+= 2 存双向边 前一条才是添加的时候的uv顺序
    for (int i = 0; i < eid; i += 2)
    {
        if (!E[i].f) continue ;
        u = E[i].u, v = E[i].v;
        //	确定uv的指向 之后diff就是我上面提到的有向的差分来统计贡献
        int cnt = dep[u] > dep[v] ? diff[1][u] : diff[0][v];
        ans = ((ans + qpow(2,cnt))%mod + mod - 1)%mod;
    }
    printf("%lld\n", ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值