CF 1034C Region Separation - dp

神仙题啊
题目大意:给你一颗树,点有点权。每轮你可以切断一些边,但要保证剩下的联通快权值和都相等。问你有多少操作方案。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)Sk.
紧接着会发现,如果切a段是可行的,切b段也是可行的,并且 a ∣ b a|b ab,那么切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);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值