Bubble Cup 8 - Finals [Online Mirror] B. Bribes · LCA+树上差分

题解

题意:n个地点,n-1条边,有单向也有双向的,给出k个连续到达的地点,允许路上逆行,但是要缴纳罚款,每次缴纳的罚款是上一次经过此地的两倍,问到达所有目的地的最小罚款金额

诶,补题补题…

看别人题解看到的,本题等价于问:

一颗树,无向边边权0,有向边边权1,逆向行走会使边权*2,问最小的边权和

做法:

建图建双向边,方便树上统计,无向边正向边反向边边权都是0,有向边正向边设置1,反向边设置-1,

先bfs处理树上关系,方便后面使用lca查树链

再树上差分标记树上经过的所有的点,由于有的边有方向,遂,将分成两颗树,一个统计自顶向下到达当前节点时的逆向罚款,一个统计自底向上时的逆向罚款,

多次经过同一个地点造成的罚款总额 = 1 + 2 1 + 2 3 + . . . + 2 p − 1 = 2 p − 1 =1+2^1+2^3+...+2^{p-1}=2^p-1 =1+21+23+...+2p1=2p1,最后统计所有逆向行驶的罚款


在这里插入图片描述


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
int n, m, k;

struct egde {
    int to, next, w;
} e[N];
int head[N], tot;
int up[N];// 到达该点时的类型 1 单行道 可以从该点出去 -1 单行道 不能从该点出去 0 都可以

void add(int u, int v, int w) {
    e[++tot] = {v, head[u], w};
    head[u] = tot;
}

const int Depth = 20;
queue<int> q;
int d[N];
int f[N][Depth + 10];
void bfs(int root) { // bfs初始化lca的f[][]
    queue<int> q;
    d[root] = 0;
    f[root][0] = root;
    up[root] = 0;
    q.push(root);
    
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = 1; i <= Depth; ++i) {
            f[u][i] = f[f[u][i - 1]][i - 1];
        }

        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (v == f[u][0])continue;
            d[v] = d[u] + 1;
            f[v][0] = u;
            up[v] = -e[i].w;//由于是树 所以不会被覆盖
            q.push(v);
        }
    }
}

int lca(int u, int v) {
    if (d[u] < d[v])swap(u, v);
    for (int i = Depth; i >= 0; --i) {
        if (d[f[u][i]] >= d[v]) u = f[u][i];
    }
    if (u == v) return u;
    for (int i = Depth; i >= 0; --i) {
        if (f[u][i] != f[v][i]) {
            u = f[u][i];
            v = f[v][i];
        }
    }
    return f[u][0];
}

int cnt1[N], cnt2[N];

void dfs(int u) {
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (v == f[u][0])continue;
        dfs(v);
        cnt1[u] += cnt1[v];//树上差分
        cnt2[u] += cnt2[v];
    }
}

ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1)res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    memset(head, -1, sizeof(head));

    cin >> n;
    for (int i = 1, u, v, w; i < n; ++i) {
        cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, -w);
    }
    bfs(1);//起点从1开始
    cin >> k;
    int last = 1, now;
    for (int i = 1; i <= k; ++i) {
        cin >> now;
        if (last == now)continue;
        int top = lca(last, now);
        cnt1[last]++;//自底向上
        cnt1[top]--;
        cnt2[now]++;//自顶向下
        cnt2[top]--;
        last = now;
    }
    dfs(1);
    ll ans = 0;//统计罚款的次数
    for (int i = 1; i <= n; ++i) {
        if (!up[i]) continue;
        if (up[i] == -1) {
            ans = (ans + qpow(2, cnt1[i]) - 1) % mod;
        } else {
            ans = (ans + qpow(2, cnt2[i]) - 1) % mod;
        }
    }
    cout << ((ans + mod) % mod) << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值