当前位置: 首页 > news >正文

题解:P14666 [KenOI 2025] 游走题

很好的数数题。

思路

观察样例,猜一个结论:游走的终点只可能是节点 \(1\)。考虑证明,一个节点如果往儿子走最终显然是可以再走回父亲的,但如果走到了父亲就不能再走回去了。所以只有走到一个没有父亲的点且把这个点的所有子树都走遍才会停止。这里的走遍指至少在这个子树中走过了一个节点。

得出这个结论之后,问题便转化为 \(1\)\(s\) 的路径必走,同时可以走到其它节点,问走过本质不同的路径长度之和。因为 \(s\)\(1\) 的路径只会走一遍,而其它的存在与路径上的点都需要走两遍,设路径总数为 \(num\)、其它存在于路径上的点的路径长度之和为 \(len\),则答案便等于:\(dis(1, s)\times num+2\times len\)

考虑通过树形 dp 计算出 \(num\)\(len\)。发现直接对整棵树进行 dp 并不容易,但如果 \(s=1\) 是很好计算的,于是想到拆贡献,最后再合并。可以先把 \(1\)\(s\) 的路径上的边删掉,然后此时原树变成了一个森林,考虑对于每一个森林中的树进行树形 dp。对于每一个树都钦定在 \(1\)\(s\) 的路径上的点 \(u\) 为根,然后每一棵树就等同于跑一遍 \(s=u\) 的树形 dp,这个东西可以直接套用 \(s=1\) 的情况。

\(f_u\) 表示以 \(u\) 为根的子树中的合法路径长度之和,\(g_u\) 表示以 \(u\) 为根的子树中的合法路径数量。

  • 如果 \(u\neq 1\),则每一个子树可以选择走和不走,所以:

\[g_u=\prod_{v\in son_u}(g_v+1) \]

  • 如果 \(u=1\),则每一个子树都必须走,所以:

\[g_u=\prod_{v\in son_u}g_v \]

接下来考虑 \(f\) 的转移。在 \(u\) 的子树中,\(f_v(v\in son_u)\) 可能和其它的 \(u\) 的儿子一起产生贡献,所以只需要知道其它儿子的组合的方案数就可以算出 \(f_u\)。同时,因为从 \(v\)\(u\) 也会产生 \(1\) 的贡献,这个贡献的前提是 \(v\) 必选,其它的儿子可以任意组合。

  • 如果 \(u\neq 1\),则每一个子树可以选择走和不走,转移为:

\[f_u=\sum_{v\in son_u} (f_v+g_v)\times g_u\div (g_v+1) \]

  • 如果 \(u=1\),则每一个子树都必须走,转移为:

\[f_u=\sum_{v\in son_u} (f_v+g_v)\times g_u\div g_v \]

最后再把 \(1\)\(s\) 的路径上的点合并即可。因为路径上的每一个点都必须选,所以是按照 \(u=1\) 的转移方式合并的。

时间复杂度:\(O(n)\)。要特判一下 \(s=1\) 的情况。

代码

#include <bits/stdc++.h>
using namespace std;using ll = long long;
using ld = long double;
using ull = unsigned long long;
using i128 = __int128;using PII = pair<int, int>;
using PLL = pair<ll, ll>;constexpr ll inf = (1ll << 62);
constexpr int N = 1e6 + 10;template <typename T>
concept Can_bit = requires(T x) { x >>= 1; };
template <int MOD>
struct modint {int val;static int norm(const int& x) { return x < 0 ? x + MOD : x; }static constexpr int get_mod() { return MOD; }modint inv() const {assert(val);int a = val, b = MOD, u = 1, v = 0, t;while (b > 0) t = a / b, swap(a -= t * b, b), swap(u -= t * v, v);assert(b == 1);return modint(u);}modint() : val(0) {}modint(const int& m) : val(norm(m)) {}modint(const long long& m) : val(norm(m % MOD)) {}modint operator-() const { return modint(norm(-val)); }bool operator==(const modint& o) { return val == o.val; }bool operator<(const modint& o) { return val < o.val; }modint& operator+=(const modint& o) { return val = (1ll * val + o.val) % MOD, *this; }modint& operator-=(const modint& o) { return val = norm(1ll * val - o.val), *this; }modint& operator*=(const modint& o) { return val = static_cast<int>(1ll * val * o.val % MOD), *this; }modint& operator/=(const modint& o) { return *this *= o.inv(); }modint& operator^=(const modint& o) { return val ^= o.val, *this; }modint& operator>>=(const modint& o) { return val >>= o.val, *this; }modint& operator<<=(const modint& o) { return val <<= o.val, *this; }modint operator-(const modint& o) const { return modint(*this) -= o; }modint operator+(const modint& o) const { return modint(*this) += o; }modint operator*(const modint& o) const { return modint(*this) *= o; }modint operator/(const modint& o) const { return modint(*this) /= o; }modint operator^(const modint& o) const { return modint(*this) ^= o; }modint operator>>(const modint& o) const { return modint(*this) >>= o; }modint operator<<(const modint& o) const { return modint(*this) <<= o; }friend std::istream& operator>>(std::istream& is, modint& a) {long long v;return is >> v, a.val = norm(v % MOD), is;}friend std::ostream& operator<<(std::ostream& os, const modint& a) { return os << a.val; }friend std::string tostring(const modint& a) { return std::to_string(a.val); }template <Can_bit T>friend modint qpow(const modint& a, const T& b) {assert(b >= 0);modint x = a, res = 1;for (T p = b; p; x *= x, p >>= 1)if (p & 1) res *= x;return res;}
};
using M107 = modint<1000000007>;
using M998 = modint<998244353>;constexpr int mod = 1e9 + 7;using Mint = M107;
// constexpr mod = ...;
// using Mint = modint<mod>;
struct Fact {std::vector<Mint> fact, factinv;const int n;Fact(const int& _n) : n(_n), fact(_n + 1, Mint(1)), factinv(_n + 1) {for (int i = 1; i <= n; ++i) fact[i] = fact[i - 1] * i;factinv[n] = fact[n].inv();for (int i = n; i; --i) factinv[i - 1] = factinv[i] * i;}Mint C(const int& n, const int& k) {if (n < 0 || k < 0 || n < k) return 0;return fact[n] * factinv[k] * factinv[n - k];}Mint A(const int& n, const int& k) {if (n < 0 || k < 0 || n < k) return 0;return fact[n] * factinv[n - k];}
};int n, s;
vector<vector<int>> G(N);
vector<Mint> f(N), g(N);
vector<int> depth(N), path;
bitset<N> flag;bool dfs1(int u, int fa) {bool ok = false;if (u == s) {ok = true;}for (auto v : G[u]) {if (v == fa) continue;depth[v] = depth[fa] + 1;ok |= dfs1(v, u);}if (ok) {path.push_back(u);}return ok;
}void dfs2(int u, int fa) {g[u] = 1;for (auto v : G[u]) {if (v == fa || flag[v]) continue;dfs2(v, u);g[u] *= (g[v] + (!u ? 0 : 1));}for (auto v : G[u]) {if (v == fa || flag[v]) continue;Mint cur = (g[v] + (!u ? 0 : 1));cur = qpow(cur, mod - 2);f[u] += (f[v] + g[v]) * g[u] * cur;}
}void solve() {cin >> n >> s;s--;for (int i = 1; i < n; i++) {int u, v;cin >> u >> v;u--;v--;G[u].push_back(v);G[v].push_back(u);}dfs1(0, -1);if (!s) {dfs2(0, -1);cout << f[0] * 2 << "\n";return;}reverse(path.begin(), path.end());for (int i = 0; i < path.size(); i++) {flag[path[i]] = true;}int cnt = (G[s].size() > 1);dfs2(0, -1);dfs2(s, -1);for (int i = 1; i < path.size() - 1; i++) {dfs2(path[i], -1);cnt += (G[path[i]].size() > 2);}Mint ans = 0, calc = 1;for (int i = 0; i < path.size(); i++) {calc *= g[path[i]];}for (int i = 0; i < path.size(); i++) {Mint cur = g[path[i]];cur = qpow(cur, mod - 2);ans += f[path[i]] * calc * cur;}Mint num = int(path.size()) - 1;cout << ans * 2 + num * calc << "\n";
}int main() {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t = 1;// cin >> t;while (t--) {solve();}return 0;
}
http://www.jsqmd.com/news/67435/

相关文章:

  • 你在用什么免费ip库?
  • Python核心容器类型教程:列表、字典、元组、集合用法与实战
  • doc-llm-autotest 基于大模型的文档自动化测试平台:worker服务的可靠性增强
  • 个人电脑本地私有知识库:访答知识库深度解析
  • 58
  • 58
  • TB710FU原厂刷机包下载_CN_ZUI_17.0.04.279_ST_250808
  • Python service Flask generate list data and display in web view via html and javscript
  • 仿真分析工具 Abaqus 2024 下载安装教程:含安装包下载 + 配置教程,新手也能一次成功
  • 香橙派上进行 Livox Mid-360 激光雷达开发(二)移植FAST_LIO
  • Mybatis拦截器原理解析
  • 10406_基于Springboot的社交平台系统
  • aaaa
  • TB331FC原厂刷机包下载_CNZUI_17.0.572_ST_250910
  • 2025云南短视频制作服务商/公司TOP5推荐!昆明等地短视频制作企业榜单发布,赋能企业品牌传播新生态
  • 2025 年 12 月杭州公寓出租权威推荐榜:精选浙江优质房源,温馨宜居与便捷交通的完美之选
  • 解码继承——代码复用与层次化设计
  • 2025年12月北京陪诊公司推荐榜:专业机构对比分析与用户选择指南
  • TB365FC刷机包_CN_ZUXOS_1.1.10.122_ST_250828
  • Python 异步编程:使用 async/await 实现高效并发 - 指南
  • 超越大语言模型:蒸馏技术实战指南
  • TB520FU刷机包_CN_17.0.10.158_ST_250817
  • web框架——flask3.x-上下文管理机制
  • JavaEE初阶——多线程(9)JUC的程序类和死锁
  • [智能体设计模式] 第 1 章:提示链(Prompt Chaining) - 实践
  • 极速AI助手 - 多AI服务桌面助手, 支持MCP工具调用, 内置免费AI功能
  • 蓝鲸花呗客服妙招帮你脱困省油大空间低配拆解银河的“水桶车细节值得吵一架
  • 吴恩达深度学习课程四:计算机视觉 第一周:卷积基础知识(一)图像处理基础
  • Python函数基础实战教程:从定义调用到参数传值全解析
  • 内旋与外旋两种旋转方式