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;
}
posted @ 2022-07-10 20:33  zqs2020  阅读(49)  评论(0编辑  收藏  举报