@codeforces - [email protected] Vladislav and a Great Legend

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

给定一棵 n 个点的树 T。对于每一个非空点集 X,定义 f(X) 为包含 X 内所有点的最小连通块的边数。
另给定一正整数 k,求:
\[\sum\limits_{X \subseteq \{1, 2,\: \dots \:, n\},\, X \neq \varnothing} (f(X))^k\]

模 10^9 + 7。

Input
第一行两个整数 n 与 k (2≤n≤10^5, 1≤k≤200),表示点数及指数。
接下来 n 行每行两个整数 ai, bi,描述一条树边 (1≤ai, bi≤n)。
Output
输出一个整数,表示结果 mod 10^9 + 7。

Examples
Input
4 1
1 2
2 3
2 4
Output
21

Input
4 2
1 2
2 3
2 4
Output
45

Input
5 3
1 2
2 3
3 4
4 5
Output
780

@[email protected]

如果以平常的思路,我们可以对于每一个 i 统计 f(X) = i 的个数。
但是我只能想到 O(n^2) 的做法,没办法进一步优化。

当 k = 1 时,我们可以想到对于每一条边,统计有多少 X 使得 X 对应的连通块包含这条边。这是很容易办到的。
当 k ≠ 1 时,注意到 k 的范围依然较小 (k <= 200) ,我们为什么不类比拓展一下:对于每个大小为 k 的边集,统计有多少 X 使得 X 对应的连通块包含这条边。
看起来好像不能够这样轻易类比。

我们考虑一个常见的套路,即斯特林反演:
\[m^k = \sum_{i=0}^{m}C(m, i)*S(k, i)*i!\]

其中 S 表示第二类斯特林数。这个证明网上挺多的,不再赘述。
注意到当 i > k 时 S(k, i) = 0,所以又有 \(m^k = \sum_{i=0}^{m}C(m, i)*S(k, i)*i!\)。
于是我们改写原式:
\[\sum\limits_{X \subseteq \{1, 2,\: \dots \:, n\},\, X \neq \varnothing} (f(X))^k = \sum_{i=0}^{k}S(k, i)*i!*(\sum\limits_{X \subseteq \{1, 2,\: \dots \:, n\},\, X \neq \varnothing} C_{f(X)}^{i})\]

我们枚举 i,则 S(k, i)*i! 可以直接算。
后面那一堆的组合意义实际上是 “从树上选出一个大小为 i 的边集,有多少点集 X 对应的连通块包含这个边集”。
我们就实现了我们最初的目的。

我们可以通过树形 dp 来统计那个东西。
对于每个点集 X,我们在 X 内所有点的 LCA 处统计这个 X 的相关信息。
因此为了方便转移,我们定义 f[i][j] 表示以 i 为根的子树中,点集 X 的 LCA 高于或等于 i,已经选中 j 条边的方案数。

统计 LCA 为 i 时选出 j 条边的方案时,如果新加进来一个子树 p,以前的子树与根 i 构成连通块为 q,则新增的方案 del 等于 p 中、 q 中、p 与根 i 之间的连边选中共 j 条边的方案。
讨论一下就可以转移了。
f 的转移与上面大致相似,不过需要注意的是,f 可能会不选 p 中的点(即对应在 p 中选个空集)。需要单独拿出来讨论。

需要注意的是,为了不让时间复杂度退化成 O(nk^2),我们不能直接枚举到 k,而应该枚举到 min(size, k),其中 size 是子树的大小。这样复杂度是 O(nk)。
这个其实是一般的树形 dp 的一个优化的套路。不过一般的树形 dp 的证明只需要知道每个点对只会在 LCA 处统计即可得证,而这个证明要麻烦一些。

(以下转载自这一篇博客)。

首先考虑若干个 <k 的子树进行合并直到合并为一个 ≥k 的子树,代价是O(k^2)的,最多合并成n/k个这样的,于是复杂度是O(nk)。
再考虑<k的与≥k的合并,复杂度是O(size?k)的,那么由于每一点最多经历一次这个过程,复杂度也是O(nk)。
最后是两个≥k的子树合并,由于只有最多n/k个这样的子树因此操作次数不超过nk,因此复杂度是O(nk)。
于是最终的复杂度是O(nk),降低复杂度的关键是合并的代价可以和子树大小取min。

@accepted [email protected]

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
const int MAXK = 200;
const int MOD = int(1E9) + 7;
inline int add(int a, int b) {return (a + b)%MOD;}
inline int mul(int a, int b) {return 1LL*a*b%MOD;}
struct edge{
    int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = &edges[0];
void addedge(int u, int v) {
    edge *p = (++ecnt);
    p->to = v, p->nxt = adj[u], adj[u] = p;
    p = (++ecnt);
    p->to = u, p->nxt = adj[v], adj[v] = p;
}
int n, k;
int res[MAXK + 5], fct[MAXK + 5], S[MAXK + 5][MAXK + 5];
int func() {
    int ans = 0;
    for(int i=0;i<=k;i++)
        ans = add(ans, mul(mul(S[k][i], fct[i]), res[i]));
    return ans;
}
void init() {
    S[0][0] = 1;
    for(int i=1;i<=k;i++) {
        S[i][0] = 0;
        for(int j=1;j<=i;j++)
            S[i][j] = add(S[i-1][j-1], mul(S[i-1][j], j));
    }
    fct[0] = 1;
    for(int i=1;i<=k;i++)
        fct[i] = mul(fct[i-1], i);
}
int f[MAXK + 5][MAXN + 5], siz[MAXN + 5];
void dfs(int x, int fa) {
    f[0][x] = 1, res[0] = add(res[0], 1), siz[x] = 1;
    for(edge *p=adj[x];p;p=p->nxt) {
        if( p->to == fa ) continue;
        dfs(p->to, x);
        int t1 = min(siz[p->to] - 1, k), t2 = min(siz[x] - 1, k), t3 = min(siz[p->to] + siz[x] - 1, k);
        for(int i=t2;i>=0;i--)
            for(int j=min(t3-i, t1);j>=0;j--) {
                int del = mul(f[i][x], f[j][p->to]);
                res[i+j] = add(res[i+j], del), f[i+j][x] = add(f[i+j][x], del);
                if( i+j+1 <= t3 )
                    res[i+j+1] = add(res[i+j+1], del), f[i+j+1][x] = add(f[i+j+1][x], del);
            }
        for(int i=0;i<=t1;i++) {
            f[i][x] = add(f[i][x], f[i][p->to]);
            if( i + 1 <= t3 )
                f[i + 1][x] = add(f[i + 1][x], f[i][p->to]);
        }
        siz[x] += siz[p->to];
    }
}
int main() {
    scanf("%d%d", &n, &k), init();
    for(int i=1;i<n;i++) {
        int a, b; scanf("%d%d", &a, &b);
        addedge(a, b);
    }
    dfs(1, 0);
    printf("%d\n", func());
}

@[email protected]

所以树形 dp 往往是听上去容易,代码细节很多的类型。
看代码要容易理解一些吧。。。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11621730.html

时间: 2024-08-30 18:17:09

@codeforces - [email protected] Vladislav and a Great Legend的相关文章

@codeforces - [email&#160;protected] Mashmokh&#39;s Designed Problem

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,每个点的儿子是有序的. 现给定 m 次操作,每次操作是下列三种中的一种: (1)给定 u, v,询问 u, v 之间的距离. (2)给定 v, h,断开 v 到父亲的边,将 v 这棵子树加入到它的第 h 个祖先的最后一个儿子. (3)给定 k,询问在当前这棵树上

@codeforces - [email&#160;protected] T-Shirts

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 件 T-shirt,第 i 件 T-shirt 有一个 ci 和 qi,分别表示费用与质量. 同时有 k 个顾客,第 j 个顾客准备了 bj 的金钱去购买 T-shirt. 每个顾客的购买策略是相同的: 他会买他的资金范围内 q 值最大的一件,如果有多个选 c 最小的一件,每种

@codeforces - [email&#160;protected] Oleg and chess

目录 @description - [email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @description - [email protected] 给定一个 n*n 的棋

@codeforces - [email&#160;protected] Lucky Tickets

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 已知一个数(允许前导零)有 n 位(n 为偶数),并知道组成这个数的数字集合(并不一定要把集合内的数用完).求有多少种可能,使得这个数前半部分的数位和等于后半部分的数位和. 模 998244353. input 第一行两个整数:n k.表示这个数的位数以及组成这个数的数字集合大小.2

@codeforces - [email&#160;protected] Bandit Blues

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 求有多少个长度为 n 的排列,从左往右遍历有 a 个数比之前遍历的所有数都大,从右往左遍历有 b 个数比之前遍历的所有数都大. 模 998244323. input 一行三个整数 n

@codeforces - [email&#160;protected] Vus the Cossack and a Field

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的 01 矩阵,通过这个矩阵生成一个无穷矩阵,具体操作如下: (1)将这个矩阵写在左上角. (2)将这个矩阵每位取反写在右上角. (3)将这个矩阵每位取反写在左下角. (4)将这个矩阵写在右下角. (5)将得到的矩阵再作为初始矩阵,重复这些操作. 比如对于初始矩阵:

@codeforces - [email&#160;protected] Big Problems for Organizers

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个点连成一棵树,经过每条边需要花费 1 个单位时间. 现给出 m 次询问,每次询问给出两个点,需要求所有点同时出发,最终所有点到达这两个点之一的最小花费时间. input 第一行包含一个整数 n (2?≤?n?≤?100000) ,表示点数. 接下来 n-1 行每行两个 1~n 的

@codeforces - [email&#160;protected] Strongly Connected Tournament

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个选手参加了一场竞赛,这场竞赛的规则如下: 1.一开始,所有选手两两之间独立进行比赛(没有平局). 2.主办方将胜者向败者连边形成 n 个点的竞赛图. 3.主办方对这个竞赛图进行强连通分量缩点. 4.每一个强连通分量内部的选手重复步骤 1~3,直到每一个强连通分量内只剩一个选手.

@codeforces - [email&#160;protected] Rotate Columns (hard version)

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的矩阵 A. 定义一次操作为将矩阵的某一列竖着循环移位,你可以对任意列做任意次操作. 定义 ri 为第 i 行的最大值,最大化 r1 + r2 + ... + rn. Input 第一行一个整数 t (1≤t≤40),表示数据组数. 每组数据第一行包含两个整数 n m