(HDUOJ7452)2024HDU暑假多校2-1008-成长,生命,幸福 题解

原题链接

首先可以知道如果一个点在最后答案的路径里, 对其进行成长后, 多的那些点也全部会在答案的路径上的. 我们可以通过总是选择断开原来图中就有的边, 而让路径在新生长的这个圈中绕一圈来做到. 也就是说, 如果一个点在答案的路径上, 那么它成长 M 次后的总点数就是他的贡献.

考虑如何快速算出成⻓后的点数, 可以发现, 在进行一次成⻓后的树度数最大为3, 对于一个 2 度点, 成⻓一次变为 2 个 2 度点, 对于一个 3 度点, 成⻓一次边为 2 个 2 度点和 1 个 3 度点,对于一个 d 度点, 成⻓一次为 2 个 2 度点和 d − 2 d-2 d2 个 3 度点. 最后推出一个度数为 x 的点成长 m 次后会变成 ( x − 1 ) ∗ ( 2 m − 1 ) + 1 (x-1)*(2^m-1)+1 (x1)(2m1)+1个点.(补题时总是不想计算这些推理的, 所有我没推这玩意 )

接下来就考虑用这个式子作为每个点的权值, 来计算树中的最长路( 权值和最大的路径 ).

  1. 当 m 很小时, 运算结果不会超过 1 e 9 + 7 1e9+7 1e9+7, 我们可以直接计算出每个点的贡献来找最长路, 代码用的是找树的直径的 DP 算法, 也可以两遍 dfs 做, 详细讲解看 io wiki.
 int Max = 0;//要的答案
 vector<int>ans(n + 1, 0); // 子树中从子树的根到某个叶子的最长路
 auto dfs = [&](auto self, int x, int fa) ->void {
     for (auto c : eg[x]) {
         if (c == fa)continue;
         self(self, c, x);
         Max = max(Max, ans[x] + ans[c] + (du[x] - 1) * (ksm(2, m) - 1) + 1);
         ans[x] = max(ans[x], ans[c]);
     }
     ans[x] += (du[x] - 1) * (ksm(2, m) - 1) + 1;
 };
 dfs(dfs, 1, 1);
 return Max;
  1. 当 m 很大时, 由于取模, 求得的结果就不具备可比大小的性质了.
    但是观察 w = ( x − 1 ) ∗ ( 2 m − 1 ) + 1 w=(x-1)*(2^m-1)+1 w=(x1)(2m1)+1这个式子, 会发现: 当 m > 20 m>20 m>20 时, 2 m 2^m 2m为 1e6 级别, 而后面的 1 在对所有点的贡献提取公因式后最大不会超过点的个数 1e5.
    也就是说: 度数 x 每增大 1, 后面的常数变再大也影响不到最终答案是不是 Max. 就可以用 ( 2 m − 1 ) (2^m-1) (2m1)的系数 => (路径的度数和减去路径的点数) 为第一关键字, 点的个数(也就是公式中常数 1 的个数 ) 为第二关键字, 作为权值, 就最长路, 最后再算答案. 为什么 m 较小时不可以这样做呢? 因为 Max 的度数和比较小者大 1,但较小者可能由很多点组成, 也就是最后的常数非常大, 2 m − 1 < n 2^m-1<n 2m1<n时, 不能保证结果不会被影响.

eg: 第一关键字的"减去路径的点数"不能拿掉, 因为它使得第一关键字的大小受点的个数影响, 而有相同度数和的路径可能具有不用的点个数.

全部代码:

#define int long long
const int M = 1e9 + 7;
int ksm(int a, int n) {
    int ans = 1;
    while (n) {
        if (n % 2)ans = ans * a % M;
        a = a * a % M;
        n /= 2;
    }
    return ans;
}

int solve(int _) {
    int n, m, v, u;
    cin >> n >> m;
    vector<int>du(n + 1, 0);
    vector<vector<int>>eg(n + 1);
    for (int i = 1; i < n; ++i) {
        cin >> v >> u;
        eg[u].emplace_back(v);
        eg[v].emplace_back(u);
        du[u]++;
        du[v]++;
    }
    if (m < 20) {
        int Max = 0; //一位度数-数量, 二维数量
        vector<int>ans(n + 1, 0);
        auto dfs = [&](auto self, int x, int fa) ->void {
            for (auto c : eg[x]) {
                if (c == fa)continue;
                self(self, c, x);
                Max = max(Max, ans[x] + ans[c] + (du[x] - 1) * (ksm(2, m) - 1) + 1);
                ans[x] = max(ans[x], ans[c]);
            }
            ans[x] += (du[x] - 1) * (ksm(2, m) - 1) + 1;
        };
        dfs(dfs, 1, 1);
        return Max;
    }
    else {
        pair<int, int>Max;//一位度数-数量, 二维数量
        vector<pair<int, int>>dep(n + 1, {0, 0});
        auto dfs = [&](auto self, int x, int fa) -> pair<int, int> {
            for (auto c : eg[x]) {
                if (c == fa)continue;
                auto tp = self(self, c, x);
                Max = max(Max, {dep[x].first + tp.first + du[x] - 1, dep[x].second + 1 + tp.second});
                dep[x] = max(dep[x], tp);
            }
            dep[x].first += du[x] - 1;
            dep[x].second++;
            return dep[x];
        };
        dfs(dfs, 1, 1);
        return (Max.first * (ksm(2, m) - 1 + M) % M + Max.second) % M;
    }
}
signed main()
{
    // freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    int _ = 1;
    cin >> _;
    while (_--) {
        cout << solve(_) << endl;
    }
}
  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值