CF1034C Region Separation
题面确实很吓人,先考虑只有一次划分怎么做.
设 \(s_i\) 表示 \(i\) 子树点权和,\(S\) 表示整棵树点权和。如果分成 \(k\) 个区域,那么每个连通块 \(k\) 个点权和为 \(\frac{s}{k}\)。
注意到一个性质:满足 \(\frac{s}{k}\vert s_i\) 的 \(i\) 最多只有 \(k\) 个。证明显然。
还有一个推论,就是根据上面的性质,把这颗树一次划分为 \(k\) 个区域方案唯一。
根据这个性质不难发现要找的就是满足 \(\frac{s}{k}\vert s_i\) 的 \(i\) 的个数。
显然不能枚举 \(k\),考虑对每个 \(i\) 计算它对哪些 \(k\) 有贡献。化一下式子可以得到,\(\frac{s}{k}\vert s_i\Longleftrightarrow \frac{S}{\gcd(S,s_i)}\vert k\)。
因此把所有 \(\frac{S}{\gcd(S,s_i)}\) 记下来最后调和级数复杂度暴力枚举倍数累加就行了。
然后拓展到多次划分的情况。
根据上面的推论,对于若干次划分后划为 \(k\) 个区域的方案,上一次划分区域数必定是它的约数。并且显然地,如果上次划分区域数是它的约数,那么一定可以再次划分为 \(k\) 个区域。即,不同划分之间互不影响,只需要满足每次划分区域数的关系即可。
那么若干次划分后划为 \(k\) 个区域的方案数也可以调和级数复杂度求出了。
时间复杂度 \(O(n\log n)\)。
#include <cstdio>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++)
const int mod = 1e9 + 7;
inline void add(int &x, const int y) {
if ((x += y) >= mod) x -= mod;
}
char buf[100000], *p1, *p2;
inline int read() {
char ch;
int x = 0;
while ((ch = gc) < 48);
do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
return x;
}
long long gcd(long long n, long long m) {return m ? gcd(m, n % m) : n;}
int fa[1000005], n;
long long s[1000005];
int f[1000005], g[1000005];
int main() {
n = read();
for (int i = 1; i <= n; ++ i) s[i] = read();
for (int i = 2; i <= n; ++ i) fa[i] = read();
for (int i = n; i; -- i) s[fa[i]] += s[i];
for (int i = 1; i <= n; ++ i) {
long long x = s[1] / gcd(s[1], s[i]);
if (x <= n) ++ f[x];
}
for (int i = n; i; -- i)
for (int j = i + i; j <= n; j += i) add(f[j], f[i]);
for (int i = 1; i <= n; ++ i) f[i] = (f[i] == i);
g[1] = 1;
for (int i = 1; i <= n; ++ i) if (f[i])
for (int j = i + i; j <= n; j += i) add(g[j], g[i]);
int ans = 0;
for (int i = 1; i <= n; ++ i) add(ans, g[i] * f[i]);
printf("%d", ans);
return 0;
}