神仙题啊
题目大意:给你一颗树,点有点权。每轮你可以切断一些边,但要保证剩下的联通快权值和都相等。问你有多少操作方案。1e6。
题解:考虑给你一个k问你能不能切成k段。
你会发现方案是唯一的:不断的从小到大考虑子树,每次遇到恰好k就切掉。
但这么做没有前途。我们发现,假设所有点权值之和是S,那么只有那些子树权值在模
S
k
\frac Sk
kS意义下为0才有用。如果这样的子树有不少于k个那么就可行(因为你总是可以随便切k-1刀把树分成k段,这样由于每个连通块点权至少时
S
k
\frac Sk
kS,因此一定是恰好每个连通块是
S
k
\frac Sk
kS,其实这也说明了满足这样条件的子树也最多也只有k个)。
考虑一个子树权值和为t,那么对k有贡献当且进当
t
=
x
S
k
,
t
S
=
x
k
,
S
gcd
(
S
,
t
)
∣
k
t=x\frac Sk,\frac tS=\frac xk,\left.\frac{S}{\gcd(S,t)}\right|k
t=xkS,St=kx,gcd(S,t)S∣∣∣k.
紧接着会发现,如果切a段是可行的,切b段也是可行的,并且
a
∣
b
a|b
a∣b,那么切a段的方案一定是切b段的方案的子集。到这里dp一下就可以了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define lint long long
#define mod 1000000007
#define upd(a,b) (a+=b,(a>=mod?a-=mod:0))
#define N 1000010
#define gc getchar()
using namespace std;
inline int inn()
{
int x,ch;while((ch=gc)<'0'||ch>'9');
x=ch^'0';while((ch=gc)>='0'&&ch<='9')
x=(x<<1)+(x<<3)+(ch^'0');return x;
}
lint gcd(lint a,lint b) { return a?gcd(b%a,a):b; }
lint s[N];int f[N],p[N],ans[N];
int main()
{
int n=inn(),Ans=0;
for(int i=1;i<=n;i++) s[i]=inn();
for(int i=2;i<=n;i++) p[i]=inn();
for(int i=n;i;i--) s[p[i]]+=s[i];
for(int i=n;i;i--) s[i]=s[1]/gcd(s[1],s[i]);
for(int i=1;i<=n;i++) if(s[i]<=n) f[s[i]]++;
for(int i=n;i;i--) for(int j=2*i;j<=n;j+=i) upd(f[j],f[i]);
for(int i=ans[1]=1;i<=n;i++) if(f[i]>=i)
for(int j=i*2;j<=n;j+=i) upd(ans[j],ans[i]);
for(int i=1;i<=n;i++) if(f[i]>=i) upd(Ans,ans[i]);
return !printf("%d\n",Ans);
}