Codeforces Round #511 (Div. 1) C. Region Separation(dp + 数论)

题意

一棵 n 个点的树,每个点有权值 ai 。你想砍树。

你可以砍任意次,每次你选择一些边断开,需要满足砍完后每个连通块的权值和是相等的。求有多少种砍树方案。

n106,ai109

题解

先假设只砍一次。令所有点权和为 S ,那么假设要砍成 k 个连通块,则每个连通块的权值和均为 Sk

考虑如何得到砍的方案,以 1 号点为根 dfs ,若当前点 i 的子树之和 Sk|sumi ,则当前子树可以砍下来。若最后恰好砍了 k 次,那么就得到了一个合法的砍树方案。

其实这就等价于 i=1n[Sk|sumi]=k

不难看出这个对应且仅对应一种方案。如果不足 k ,那么就没有那么多个点可以分;多于 k 的情况是不可能的,因为总和不够分配。

这个式子还不够优秀,我们转化一下:

(1)[Sk|sumi]=[S|k×sumi](2)=[Sgcd(S,sumi)|k×sumigcd(S,sumi)](3)Sgcd(S,sumi)sumigcd(S,sumi)(4)=[Sgcd(S,sumi)|k]

然后就变成

i=1n[Sgcd(S,sumi)|k]=k

显然这个我们可以枚举倍数在 O(nlnn) 的时间内解决(注意 kn

那么如果砍多次呢?可以看出如果第一次砍成了 x 块,那么第二次砍成的块数 y 必须满足 x|y

因为你之后的权值只能比之前分的更多,且每个联通块的权值是之前的一个因子。

这部分也可以 O(nlnn) 算。

总结

熟悉这种分成很多块有关于 O(lnn) 复杂度的东西就行啦qwq

代码

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long long ll; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("C.in", "r", stdin); freopen ("C.out", "w", stdout); #endif } const int N = 1e6 + 1e3; bitset<N> pass; ll sum[N], dp[N]; int n, fa[N]; int main () { File(); n = read(); For (i, 1, n) sum[i] = read(); For (i, 2, n) fa[i] = read(); Fordown (i, n, 1) sum[fa[i]] += sum[i]; For (i, 1, n) { ll tmp = sum[1] / __gcd(sum[1], sum[i]); if (tmp <= n) ++ dp[tmp]; } Fordown (i, n, 1) if (dp[i]) for (int j = i * 2; j <= n; j += i) dp[j] += dp[i]; For (i, 1, n) pass[i] = (dp[i] == i && !(sum[1] % i)), dp[i] = 0; dp[1] = pass[1]; ll ans = 0; For (i, 1, n) if (pass[i]) { for (int j = i * 2; j <= n; j += i) if (pass[j]) dp[j] += dp[i]; ans += dp[i]; } printf ("%lld\n", ans); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9773420.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(514)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示