题目描述:你有一棵 \(n\) 个节点的树,点带权,每次可以砍掉一些边,使得每个联通块的权值和相等,求砍树方案数。
数据范围:\(n\le 10^6,a_i\le 10^9\)。
设 \(s_x=\sum_{i在x子树内} a_i\),\(S=s_{rt}\)。
首先假设只有一次操作,要分割成 \(k\) 部分,那么要么无解要么方案唯一。
按子树权值和从小到大考虑,如果当前子树权值和 \(<\frac{S}{k}\) 则不分割,\(=\frac{S}{k}\) 就要分割,否则无解。
还有一种考虑的方法,我们只能割掉 \(\frac{S}{k}|f_x\) 的 \(x\) 与其父亲的边,设 \(f_k=\sum_x[\frac{S}{k}|f_x]\),则 \(f_k\le k\)。所以有解当且仅当 \(f_k=k\)。为什么我猜到这个结论还以为是错的啊...
如何求出 \(f_k\) 呢,则若 \(\frac{S}{k}|s_i\) 时,\(s_i\) 对 \(k\) 造成贡献,也就是 \(\frac{S}{k}|\gcd(s_i,S)\),即 \(\frac{S}{\gcd(s_i,S)}|k\)。所以只需要枚举倍数即可。
设最后一次剖分为 \(k\) 份时的答案为 \(dp_k\),则
\[dp_k=[f_k=k]\sum_{t|k}dp_t \]
总时间复杂度 \(O(n\log n)\)。
#include
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair pii;
const int N = 1000003, mod = 1e9 + 7;
inline int ksm(int a, int b){
int res = 1;
for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
return res;
}
template
inline void read(T &x){
int ch = getchar(); x = 0; bool f = false;
for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
if(f) x = -x;
}
template
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
LL gcd(LL a, LL b){return b ? gcd(b, a % b) : a;}
inline void qmo(int &x){x += (x >> 31) & mod;}
int n, fa[N], f[N], dp[N], ans;
LL s[N];
int main(){
read(n);
for(Rint i = 1;i <= n;++ i) read(s[i]);
for(Rint i = 2;i <= n;++ i) read(fa[i]);
for(Rint i = n;i >= 2;-- i) s[fa[i]] += s[i];
for(Rint i = 1;i <= n;++ i){
LL x = s[1] / gcd(s[1], s[i]);
if(x <= n) ++ f[x];
}
for(Rint i = n;i;-- i) if(f[i])
for(Rint j = i << 1;j <= n;j += i) f[j] += f[i];
dp[1] = 1;
for(Rint i = 1;i <= n;++ i) if(f[i] == i){
for(Rint j = i << 1;j <= n;j += i) qmo(dp[j] += dp[i] - mod);
qmo(ans += dp[i] - mod);
}
printf("%d\n", ans);
}