Codeforces Round #511 (Div. 2) E. Region Separation(思维,性质,dp)

LINK

题意

每一轮可以砍掉若干条边,满足每个连通块权值相等,求砍树方案数.

两种方案不同,当且仅当至少存在一轮,剩余的连通块不完全相同.


考虑如果把树分成 k k k份,那么每个连通块的权值为 S k \frac{S}{k} kS

我们 d f s dfs dfs这棵树,若存在一颗子树的权值等于 S k \frac{S}{k} kS,显然需要砍掉这个儿子和父亲的边

这样重复做下去,发现需要砍掉所有满足 S k ∣ s u \frac{S}{k}|s_u kSsu u u u与父亲的边

我们定义 f [ k ] f[k] f[k]表示

f [ k ] = ∑ i = 1 n [ S k ∣ s i ] f[k]=\sum\limits_{i=1}^n [\frac{S}{k}|s_i] f[k]=i=1n[kSsi]

一种划分要么无解,有解必然只有唯一解,且满足 f [ k ] = = k f[k]==k f[k]==k

若我们处理了这样的 f f f数组就知道哪些划分是有解的

定义 d p [ i ] dp[i] dp[i]表示最后一轮的结果是 k k k划分的方案数,有转移

d p [ k ] = ∑ k ∣ j d p [ j ] dp[k]=\sum\limits_{k|j}dp[j] dp[k]=kjdp[j]

问题变成求解 f f f数组来给 d p dp dp数组赋上初值

f f f数组,对于每个固定的 k k k可以花 O ( n ) O(n) O(n)的时间检验

由于 S S S很大,枚举因子的想法也不可行,复杂度达到 O ( n 2 ) O(n^2) O(n2)

f [ k ] = ∑ i = 1 n [ S k ∣ s i ] f[k]=\sum\limits_{i=1}^n [\frac{S}{k}|s_i] f[k]=i=1n[kSsi]

观察到 S k ∣ s i \frac{S}{k}|s_i kSsi的条件为

s i = a ∗ S k s_i=a*\frac{S}{k} si=akS

也就是 S k ∣ s i \frac{S}{k}|s_i kSsi, S k ∣ S \frac{S}{k}|S kSS,则有 S k ∣ g c d ( s i , S ) \frac{S}{k}|gcd(s_i,S) kSgcd(si,S)

S g c d ( S , s i ) ∣ k \frac{S}{gcd(S,s_i)}|k gcd(S,si)Sk

于是合法的 k k k容易求,只需要枚举 S g c d ( S , s i ) \frac{S}{gcd(S,s_i)} gcd(S,si)S的倍数即可

#include <iostream>
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e6+10;
const int mod = 1e9+7;
int n,f[maxn],s[maxn],dp[maxn],c[maxn];
vector<int>vec[maxn];
void dfs(int u,int fa)
{
	for( auto v:vec[u] )
	{
		if( v==fa )	continue;
		dfs( v,u );
		s[u] += s[v];
	}
}
int gcd(int a,int b){ return b==0?a:gcd(b,a%b); }
void initF()
{
	for(int i=1;i<=n;i++)
	{
		int x = s[1]/gcd( s[i],s[1] );
		if( x<=n )	c[x]++;
	}	
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j+=i)	f[j] += c[i];	
	}
}
signed main()
{
	cin >> n;
	for(int i=1;i<=n;i++)	scanf("%lld",&s[i] );
	for(int i=1;i<n;i++)
	{
		int x;	scanf("%lld",&x );
		vec[x].push_back( i+1 );
		vec[i+1].push_back( x );
	}
	dfs( 1,0 );
	initF();
	int ans = 0;
	dp[1] = 1;
	for(int i = 1;i <= n;++ i) 
		if( f[i] == i )
		{
			for(int j = i << 1;j <= n;j += i)
				dp[j] = ( dp[j] + dp[i] )%mod;
			ans = ( ans + dp[i] )%mod;
		}
	cout << ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值