题解 CF1034C【Region Separation】/ SS221116D【Xiong AK 10 IOI】

发布时间 2023-10-04 14:22:32作者: caijianhong

很妙的性质题!全是意识流证明见过吗?

problem

每次选一个非空边集删掉,谓之曰砍树。砍树后需要满足每个连通块的点权和相同。

在一个方案中可以砍很多次树,都要满足砍树后的要求。一共有多少种合法方案呢?

\(n\leq 10^6,1\leq a_i\leq 10^9\)

solution

假如我们将树砍成 \(k\) 个连通块,我们发掘一些性质:

  • 砍树方案唯一。证明:
    1. 这是棵树。
    2. 点权非负。
  • 记所有点权和为 \(S\),则有 \(k\) 个连通块的和为 \(S/k\)。(这变相要求了 \(k|S\))。
  • 如果再砍一刀,砍完后的 \(k'|k\),可以看作每个连通块独立。

考虑求出 \(f(k)\) 表示砍成 \(k\) 个连通块是不是可能的。

考虑一种贪心:从底向上,维护子树大小,如果碰到 \(siz_u=S/k\) 马上砍了,然后 \(u\) 到根的路径集体减 \(siz_u\)

猜测结论:只有恰好 \(k\)\(\frac{S}{k}|siz_u\) 才说 \(f(k)=true\)

证明
  1. 每个点唯一的包含在一个连通块中。
  2. 若一个点 \(u\) 满足 \(\frac{S}{k}|siz_u\),且 \(f(k)=true\),则子树 \(u\) 必然分成了 \(siz_u/\frac{S}{k}\) 份连通块才是正确的。请记住,点权非负。
  3. 考虑点 \(u\) 在一个连通块中是吧,我们令 \(\Sigma=\frac{S}{k}\),则一个 \(siz_u\) 可以看作是包含点 \(u\) 一个 \(\Sigma\) 和其它所有儿子满足 \(\Sigma|siz_v\) 的和。
  4. 它的本质是,每一个连通块只会算一次,刚好算了 \(k\) 次。

啊这个结论很强,但我们还是不会快速求 \(f(k)\) 呢,考虑把它变成 \(\frac{S}{\gcd(S,siz_u)}|k\)

过程

方法一

\(a=\frac{S}{k}\)

显然有 \(a|siz_u,a|S\Rightarrow a|\gcd(siz_u,S)\)

\(\therefore \frac{S}{k}|\gcd(siz_u,S)\Rightarrow \frac{S}{\gcd(S,siz_u)}|k\)

方法二

\(\frac{S}{k}|siz_u\Rightarrow \frac{S}{siz_u}|k\)

\(d=\gcd(S,siz_u)\),则分数化简 \(\frac{S/d}{siz_u/d}|k\)

现在左边这东西不是整数,考虑整除本质 \(x\frac{S/d}{siz_u/d}=k\)

\(\because k\in N^+,\therefore siz_u/d|x\),这样才能约分成整数。

考虑去掉 \(x\) 的这一部分,即 \(\frac{S\cdot siz_u/d}{siz_u}|k\Rightarrow \frac{S}{\gcd(S,siz_u)}|k\)

喵喵喵,这样我们求出 \(\frac{S}{\gcd(S,siz_u)}\) 然后枚举倍数就行了。

考虑砍了很多次之后砍成 \(k\) 个连通块的方案数:\(g_k=f(k)+\sum_{j|k,j\neq k}g_j\)

做完了。

code

点击查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=1e9+7;
void red(LL&x){x=(x%P+P)%P;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int n,fa[1000010];
LL f[1000010],siz[1000010],g[1000010],ans=0;
int main(){
	freopen("xiongaktenioi.out","w",stdout);
//	#ifdef LOCAL
	 	freopen("xiongaktenioi.in","r",stdin);
//	#endif
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&siz[i]);
	for(int i=2;i<=n;i++) scanf("%d",&fa[i]);
	for(int i=n;i>=2;i--) siz[fa[i]]+=siz[i];
	for(int i=1;i<=n;i++){
		LL w=siz[1]/gcd(siz[i],siz[1]);
		debug("siz[%d]=%lld,w=%lld\n",i,siz[i],w);
		for(LL j=w;j<=n;j+=w) f[j]++;
	}
	for(int i=n;i>=2;i--){
		if(f[i]==i){
			g[i]=1;
			for(int j=i+i;j<=n;j+=i) red(g[i]+=g[j]);
			red(ans+=g[i]);
			debug("g[%d]=%lld\n",i,g[i]);
		}
	}
	printf("%lld\n",ans+1);
	return 0;
}