题意

​ 题目链接:https://www.luogu.org/problem/P4827

​ 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \(\displaystyle \sum_{i=1}^n{\rm dist}^k(i,j)\),其中 \(\rm{dist}\) 函数表示树上两点距离。

​ \(1 \leq n \leq 50000\)

​ \(1\leq k \leq 150\)

思路

​ 看到求答案 \(k\) 次方的问题,应该联想到第二类斯特林数,因为第二类斯特林数有如下的式子:

\[n^m=\sum_{i=0}^{+\infty}{n\choose i}\left\{{m\atop i}\right\}i!
\]

​ 可以理解成,\(n^m\) 表示把 \(m\) 个不同的球放在 \(n\) 个不同的盒子中;后面的组合数表示从 \(n\) 个盒子中选出 \(i\) 个,斯特林数表示把 \(m\) 个球分成 \(i\) 个无序集合,阶乘表示排列。另外,循环上界写乘 \(n\),\(m\) 或者无穷大都是一样的。

​ 假设我们已经维护住了 \(n^m\) ,我们现在想知道 \((n+1)^m\) ,怎么办呢?套入上面的式子,我们得到:

\[\begin{align*}
(n+1)^m&=\sum_{i=0}^m{n+1\choose i}\left\{{m\atop i}\right\}i!\\
&=\sum_{i=0}^m{\huge(}{n\choose i}+{n\choose i-1}{\huge)}\left\{{m\atop i}\right\}i!
\end{align*}
\]

​ 我们发现,与 \(n^m\) 的展开式相比,\(\displaystyle \left\{{m\atop i}\right\}i!\) 没有变,每个组合数都加上前一项。这似乎在启示我们把组合数独立出来。定义数组 \(\{a_n\}\) ,用 \(a_i\) 表示 \(\displaystyle\left\{{m\atop i}\right\}i!\) 的系数,于是,我们得到了一个“数据结构”,能维护一个 \(n^m\) 形式的数,支持给 \(n\) 加一。不难发现,加上前一项的操作可以把过程逆转,于是这个“数据结构”也能支持给 \(n\) 减一,我们姑且称之为“斯特林机”(名字瞎取的,勿喷)。

​ 只能维护一个数也太鸡肋了吧?但我们不难发现,只要 \(m\) 相同,多个 \(n^m\) 形式的数可以一起维护,直接把每个斯特林机的 \(a_i\) 加在一起即可,我们可以称之为合并两个斯特林机;类似的,我们也可以从一个斯特林机中减去另一个斯特林机。这个 \(\{a_i\}\) 数组就像插值一样,用一些方便计算的量整体运算,从而得到最后结果。

​ 依靠斯特林机,这道题似乎就变得异常的简单。我们先考虑如何求出 \(1\) 号节点的答案。

​ 定义 \(dp_u\) 为 \(\displaystyle\sum_{v\in{\rm subtree(u)}}{\rm dist}^k(u,v)\) 。由于都是 \(k\) 次方,那可以把 \(dp_u\) 开成一个斯特林机的形式。初值每个节点的斯特林机中只有自己,为 \(0^0\),每次转移就对儿子的斯特林机执行各元素加一的操作,再加到自己身上。于是,我们得到了以 \(1\) 为根点答案。然后我们发现,转移中每种操作都能找到其对应的逆操作,于是我们可以很快的写出一个换根 \(dp\) 。

代码

​ 重载了很多运算符,看着挺优美的。

  1. #include<bits/stdc++.h>
  2. #define FOR(i, x, y) for(int i = (x), i##END = (y);i <= i##END; ++i)
  3. #define DOR(i, x, y) for(int i = (x), i##END = (y);i >= i##END; --i)
  4. template<typename T, typename _T>inline bool chk_min(T &x, const _T y){return y < x? x = y, 1 : 0;}
  5. template<typename T, typename _T>inline bool chk_max(T &x, const _T y){return x < y? x = y, 1 : 0;}
  6. typedef long long ll;
  7. const int N = 50005;
  8. const int P = 10007;
  9. const int M = 153;
  10. int C[M][M], S[M][M], fac[M];
  11. inline void plseq(int &x, int y) {(x += y) >= P ? x -= P : 0;}
  12. inline void mnseq(int &x, int y) {(x -= y) < 0 ? x += P : 0;}
  13. template<const int N, const int M, typename T> struct Linked_List
  14. {
  15. int head[N], nxt[M], tot; T to[M];
  16. Linked_List() {clear();}
  17. T &operator [](const int x) {return to[x];}
  18. void clear() {memset(head, -1, sizeof(head)), tot = 0;}
  19. void add(int u, T v) {to[tot] = v, nxt[tot] = head[u], head[u] = tot++;}
  20. #define EOR(i, G, u) for(int i = G.head[u]; ~i; i = G.nxt[i])
  21. };
  22. int sm;
  23. struct Stirling_Machine
  24. {
  25. int a[M];
  26. Stirling_Machine() {}
  27. Stirling_Machine(int v) {FOR(i, 0, sm) a[i] = C[v][i];}
  28. void operator +=(const Stirling_Machine &_) {FOR(i, 0, sm) plseq(a[i], _.a[i]);}
  29. void operator -=(const Stirling_Machine &_) {FOR(i, 0, sm) mnseq(a[i], _.a[i]);}
  30. void operator ++() {DOR(i, sm, 1) plseq(a[i], a[i - 1]);}
  31. void operator --() {FOR(i, 1, sm) mnseq(a[i], a[i - 1]);}
  32. int query()
  33. {
  34. int ans = 0;
  35. FOR(i, 0, sm) plseq(ans, 1ll * a[i] * S[sm][i] % P * fac[i] % P);
  36. return ans;
  37. }
  38. };
  39. Linked_List<N, N << 1, int> G;
  40. Stirling_Machine dp[N];
  41. int ans[N];
  42. int n;
  43. void dfs(int u, int f)
  44. {
  45. dp[u] = Stirling_Machine(0);
  46. EOR(i, G, u)
  47. {
  48. int v = G[i];
  49. if(v == f) continue;
  50. dfs(v, u);
  51. ++dp[v];
  52. dp[u] += dp[v];
  53. }
  54. }
  55. void redfs(int u, int f)
  56. {
  57. ans[u] = dp[u].query();
  58. EOR(i, G, u)
  59. {
  60. int v = G[i];
  61. if(v == f) continue;
  62. dp[u] -= dp[v];
  63. --dp[v];
  64. ++dp[u];
  65. dp[v] += dp[u];
  66. redfs(v, u);
  67. dp[v] -= dp[u];
  68. --dp[u];
  69. ++dp[v];
  70. dp[u] += dp[v];
  71. }
  72. }
  73. int main()
  74. {
  75. fac[0] = fac[1] = 1;
  76. FOR(i, 2, M - 1) fac[i] = 1ll * fac[i - 1] * i % P;
  77. FOR(i, 0, M - 1) FOR(j, 0, i)
  78. C[i][j] = (j == 0 || j == i ? 1 : (C[i - 1][j - 1] + C[i - 1][j]) % P);
  79. S[0][0] = 1;
  80. FOR(i, 1, M - 1) FOR(j, 1, i)
  81. S[i][j] = (S[i - 1][j - 1] + 1ll * j * S[i - 1][j]) % P;
  82. scanf("%d%d", &n, &sm);
  83. FOR(i, 1, n - 1)
  84. {
  85. int u, v;
  86. scanf("%d%d", &u, &v);
  87. G.add(u, v), G.add(v, u);
  88. }
  89. dfs(1, 0);
  90. redfs(1, 0);
  91. FOR(i, 1, n) printf("%d\n", ans[i]);
  92. return 0;
  93. }

国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)的更多相关文章

  1. [国家集训队] Crash 的文明世界(第二类斯特林数)

    题目 [国家集训队] Crash 的文明世界 前置 斯特林数\(\Longrightarrow\)斯特林数及反演总结 做法 \[\begin{aligned} ans_x&=\sum\limi ...

  2. BZOJ 2159: Crash 的文明世界 第二类斯特林数+树形dp

    这个题非常巧妙啊~ #include <bits/stdc++.h> #define M 170 #define N 50003 #define mod 10007 #define LL ...

  3. P4827 [国家集训队] Crash 的文明世界(第二类斯特林数+树形dp)

    传送门 对于点\(u\),所求为\[\sum_{i=1}^ndis(i,u)^k\] 把后面那堆东西化成第二类斯特林数,有\[\sum_{i=1}^n\sum_{j=0}^kS(k,j)\times ...

  4. 解题:国家集训队 Crash 的文明世界

    题面 这种套着高次幂的统计问题一般都要用到第二类斯特林数和自然数幂的关系:$a^k=\sum\limits_{i=0}^{k}S_k^iC_a^i*i!$ 那么对于每个点$x$有: $ans_x=\s ...

  5. 【[国家集训队] Crash 的文明世界】

    先写一个五十分的思路吧 首先这道题有一个弱化版 [POI2008]STA-Station 相当于\(k=1\),于是就是一个非常简单的树形\(dp\)的\(up\ \ and\ \ down\)思想 ...

  6. 洛谷 P4827 [国家集训队] Crash 的文明世界

    题目描述 ​ 给你一棵 n 个点的树,对于树上的每个节点 i,求 \(\sum_{j=1}^ndis(i,j)^k\).其中 \(dis(i,j)\) 为两点在树上的距离. 输入格式 ​ 第一行两个整 ...

  7. [题解] LuoguP4827 [国家集训队] Crash 的文明世界

    传送门 这个题......我谔谔 首先可以考虑换根\(dp\),但到后来发现二项式定理展开过后需要维护\(k\)个值,同时每个值也要\(O(k)\)的时间按二项式定理算 当然fft优化过后就是k lo ...

  8. [国家集训队] Crash 的文明世界

    不错的树形$ DP$的题 可为什么我自带大常数啊$ cry$ 链接:here 题意:给定一棵$ n$个节点的树,边权为$ 1$,对于每个点$ x$求$ \sum\limits_{i=1}^n dist ...

  9. [国家集训队] Crash的文明世界

    Description 给定一棵 \(n\) 个点的树,对于每个点 \(i\) 求 \(S(i)=\sum\limits_{j=1}^n \operatorname{dist(i,j)}^k\) .\ ...

随机推荐

  1. 解决移动端ios下overflow-x scroll无法隐藏滚动条的问题

    这次有个需求是在web首页添加分类菜单,一共是8个分类,在移动端水平展示,可以左右滚动. 最后在手机上微信浏览器看到是有个滚动条,非常影响美观. 主要通过以下代码实现水平滚动 white-space: ...

  2. 『The Counting Problem 数位dp』

    The Counting Problem Description 求 [L,R]内每个数码出现的次数. Input Format 若干行,一行两个正整数 L 和 R. 最后一行 L=R=0,表示输入结 ...

  3. f(n-1) + f(n-2)的编译器处理

    https://gcc.godbolt.org   int addx(int a){ return a + 2; } int gooo(){ return addx(3) + addx(4) + ad ...

  4. ASP.NET Core 开发人员异常页面

    UseDeveloperExceptionPage 中间件 我们谈谈在 Startup 类的 Configure()方法中以下代码: public void Configure(IApplicatio ...

  5. python匹配ip地址

    ip地址是用3个'.'号作为分隔符,分割4个数字,每个数字的取值在[0,255],一般日志文件中的ip地址都是有效的ip地址,不需要我们再去验证,因此,若从日志文件中提取ip,那么可以简单写成这样: ...

  6. docker 安装与基本命令

    安装 Install Docker for Linux Download Docker for Mac Install Docker for Windows 镜像是docker三大核心概念中最重要的. ...

  7. Laravel 创建指定表 migrate

    解决方案:打开创建表的那个 migration 文件,在创建表的方法执行之前加一个判断条件 if (!Schema::hasTable('password_resets')) { Schema::cr ...

  8. mongoose模糊查询

    注:nodejs服务器时候遇到了这样一个bug,就是mongoose模糊查询时候,我需要查询的数据时自定义id_(number类型)以及用户名(string类型). bug如下: nodejs服务器报 ...

  9. Gradle 创建java程序详细步骤

    Java构建工具三强: Ant, Maven, GradleAnt历史悠久, 用build.xml 描述, 当时他的xml着实让很多工程师头痛, 但仍有用武之地. Maven 用pom.xml 文件描 ...

  10. Shell脚本批量启停Docker

    目录 启动docker 停止docker Python调用脚本     最近日常测试中经常需要手动启动或停止docker,于是决定写一个Shell脚本来代替人工操作,另外该脚本,也可以通过Python ...