洛谷 P2634 [国家集训队]聪聪可可 (点分治,树形dp)

洛谷 P2634 [国家集训队]聪聪可可

题意

给一棵 n n n 个节点的树,边带权。任选两点(可相同),求两点之间简单路径长度恰好是 3 3 3 的倍数的概率。

解法

树上路径询问?立即推:点分治!

  • 枚举每个点作为lca ,维护子树的点到 lca 距离模 3 3 3 的数量,然后先遍历子树先更新答案再更新数量即可。复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
  • 当然这题也可以直接树形dp,码量一下子就小了很多,原理也是类似的。复杂度为 O ( k n ) O(kn) O(kn) k k k 为模数,本题 k = 3 k=3 k=3
代码

点分治:

#pragma region
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
#pragma endregion
const int maxn = 2e4 + 5;
int n;
vector<pair<int, int>> g[maxn];
bool vis[maxn];
int sz[maxn], d[maxn], rt, c[3];
void dfs_rt(int u, int f, int tot) {
    sz[u] = 1;
    int maxx = 0;
    for (auto e : g[u]) {
        int v = e.first;
        if (vis[v] || v == f) continue;
        dfs_rt(v, u, tot);
        sz[u] += sz[v];
        maxx = max(maxx, sz[v]);
    }
    maxx = max(maxx, tot - sz[u]);
    if (maxx * 2 <= tot) rt = u;
}
int cnt;
void dfs_ans(int u, int f, int &ans) {
    ++cnt;
    ans += c[(3 - (d[u] % 3)) % 3] + (d[u] % 3 == 0);
    for (auto e : g[u]) {
        int v = e.first, w = e.second;
        if (vis[v] || v == f) continue;
        d[v] = d[u] + w;
        dfs_ans(v, u, ans);
    }
}
void dfs_c(int u, int f) {
    c[d[u] % 3]++;
    for (auto e : g[u]) {
        int v = e.first;
        if (vis[v] || v == f) continue;
        dfs_c(v, u);
    }
}
int work(int u, int f, int tot) {
    dfs_rt(u, f, tot);
    u = rt, vis[u] = 1, d[u] = 0;
    int ans = 0;
    for (auto e : g[u]) {
        int v = e.first, w = e.second;
        if (vis[v]) continue;
        cnt = 0, d[v] = w;
        dfs_ans(v, u, ans);
        sz[v] = cnt;
        dfs_c(v, u);
    }
    c[0] = c[1] = c[2] = 0;
    for (auto e : g[u]) {
        int v = e.first;
        if (vis[v]) continue;
        ans += work(v, u, sz[v]);
    }
    return ans;
}
int main() {
    scanf("%d", &n);
    rep(i, 1, n - 1) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    int ans = work(1, 0, n) * 2 + n, sum = n * n;
    int g = __gcd(ans, sum);
    printf("%d/%d\n", ans / g, sum / g);
}

树形dp:

#pragma region
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
#pragma endregion
const int maxn = 2e5 + 5;
int n;
vector<pair<int, int>> g[maxn];
int dp[maxn][3], ans;
void dfs(int u, int f) {
    dp[u][0] = 1;
    for (auto e : g[u]) {
        int v = e.first, w = e.second;
        if (v == f) continue;
        dfs(v, u);
        rep(i, 0, 2) ans += 2 * (dp[v][i] * dp[u][((3 - i - w) % 3 + 3) % 3]);
        rep(i, 0, 2) dp[u][(i + w) % 3] += dp[v][i];
    }
}
int main() {
    scanf("%d", &n);
    rep(i, 1, n - 1) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    dfs(1, 0);
    int sum = n * n;
    ans += n;
    int g = __gcd(ans, sum);
    printf("%d/%d\n", ans / g, sum / g);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值