Illublog我多想说再见啊

CF917D Stranger Trees

Illu·2022-07-11 10:03·30 次阅读

CF917D Stranger Trees

复盘 nealchen 神仙讲的数数题。

Warning:如果你做过「WC2019」数树 的话你可以尝试先不看题解,直接去想 O(n2) 的做法。

这题还算是弱化版的呢(

UPDATE:“不少于”仅是便于理解,更严谨的说法就是“钦定”,因为二项式反演内存在系数。其他比如子集反演就是真正意义上的“不少于”。

(感谢 Constant 的纠正)

Description#

给定一棵有 n 个点的树,对于所有 k[0,n1] ,求有 k 条边和模板树重合的树有多少个。

答案对 109+7 取模。

原题:n100

加强版:n5000

Analysis#

我是不会告诉你我连 O(n4) 都不会做的(

大概思路就是利用 矩阵树 ,因为考虑到一般的 矩阵树 处理不了恰好 k 条边的类型。

所以我们可以利用类似 子集卷积 的思维,我们单独令树边的权值带上未知数 x

那么最终只需要在 矩阵树 上找 xk 的地方就行了。

(大概是 多项式插值+矩阵树 之类的牛逼算法,我显然不会)

显然还不够精髓,看起来有点科技暴力(

那么假如我们已经钦定好 k 条边重合,根据树的性质,就还会剩下 nk2 个联通块。

一个常用 trick:对于这样的森林,假定它们的大小是 sizi ,它存在的树的方案数:

nnk2i=1ksizi

(证明是用 矩阵树 或者 Prufer 序列,不会但是挺好记的)

那么其实对于有 k 条边重合的情况,需要知道的就是所有可能的 siz

复杂度直接上天。

考虑 DP。

Solution#

(已经会和「WC2019」数树 sub2 大量相同了)

我们发现 DP 是能结合大量状态,但是却难以记录恰好 k 条边的限制。

有个比较自然的想法,恰好 k 条边不太行,但是改成钦定 k 条边呢?

这很二项式反演,令“恰好”是 fk ,“钦定”是 gk

gk=i=kn(ik)fi

fk=i=kn(1)ik(ik)gi

于是我们设 dpu,i,k 表示在 u 为根的子树中, u 所在联通块大小为 i ,且钦定了 k 条边重合的答案。(因为其他联通块还有重合所以即为钦定 k 条边

那么对于树上的每一条边 (u,v) ,我们考虑选或者不选:

  1. 选:dpu,i+j,k+l+1=dpu,i,kdpv,j,l

  2. 不选:dpu,i,k+l=dpu,i,kdpv,j,ll

因为枚举 uv 的子树最终只是 O(n2) 的。

然后总共是 O(n4) 的。


然后一通观察发现转移性是比较单一,多项式插值可以减掉一个 n ,但是我们并不满足。


考虑 trick 中 iai 组合意义,大概就是每个联通块选择一个“特殊点”的总方案数。

那其实对于当前联通快,我们并不关心其他地方究竟选了那些“特殊点”,只关心目前的联通块选了“特殊点”没有。

所以可以把联通快那一维替换成 0/1

注意:

  1. 这样的话假如边 (u,v) 要合并,两个联通块不能同时选过“特殊点”的。(可以理解为“加量”)

  2. 假如不合并,必须要求 v 所在的联通快已经选过“特殊点”了(可以理解为“结算”)

直接 O(4) 转移,总时间就变到 O(n2) 的了。

Code#

Code
Copy
/* */ #include using namespace std; #define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout); #define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout); typedef long long ll; typedef unsigned long long ull; typedef std::pair pii; #define fi first #define se second #define mp std::make_pair const int mod = 1e9 + 7; template inline int M(A x) {return x;} template inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;} const int N = 5e3 + 10; int n, fst[N], tot; struct edge {int nxt, to;} e[N << 1]; inline void add(int u, int v) { e[++tot] = (edge) {fst[u], v}; fst[u] = tot; e[++tot] = (edge) {fst[v], u}; fst[v] = tot; } int f[N][N][2], g[N][N], si[N]; inline void dfs(int u, int fa) { f[u][0][0] = f[u][0][1] = si[u] = 1; for (int i = fst[u], v; i; i = e[i].nxt) { v = e[i].to; if (v == fa) continue; dfs(v, u); for (int j = 0; j <= si[u]; ++j) { for (int k = 0; k <= n - si[u] && k <= si[v]; ++k) { for (int x = 0; x < 2; ++x) { for (int y = 0; x + y < 2; ++y) { g[j + k + 1][x | y] += M(f[u][j][x], f[v][k][y]); (g[j + k + 1][x | y] >= mod) && (g[j + k + 1][x | y] -= mod); } g[j + k][x] += M(f[u][j][x], f[v][k][1]); (g[j + k][x] >= mod) && (g[j + k][x] -= mod); } } } si[u] += si[v]; for (int j = 0; j <= si[u]; ++j) { for (int k = 0; k < 2; ++k) { f[u][j][k] = g[j][k]; g[j][k] = 0; } } } } int ret[N], res = 1, C[N][N]; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cin >> n; for (int i = 1, u, v; i < n; ++i) { std::cin >> u >> v; add(u, v); } dfs(1, 0); ret[n - 1] = 1; for (int i = n - 2; ~i; --i) { ret[i] = M(res, f[1][i][1]); res = M(res, n); } for (int i = 0; i < n; ++i) { int ans = 0; for (int j = i; j <= n; ++j) { if (!i) C[j][i] = 1; else { C[j][i] = C[j - 1][i - 1] + C[j - 1][i]; (C[j][i] >= mod) && (C[j][i] -= mod); } res = M(C[j][i], ret[j]); if ((j - i) & 1) ans += mod - res; else ans += res; (ans >= mod) && (ans -= mod); } std::cout << ans << " "; } return 0; }

posted @   Illusory_dimes  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示
目录