【树上启发式合并】牛客:合约数(数学+奇怪的记录方式)

【树上启发式合并】牛客:合约数(数学+奇怪的记录方式)

题意:

给定一棵n个节点的树,并且根节点的编号为p,第i个节点有属性值vali, 定义F(i): 在以i为根的子树中,属性值是vali的合约数的节点个数。y 是 x 的合约数是指 y 是合数且 y 是 x 的约数。小埃想知道 ∑ i = 1 n i ⋅ F ( i ) \sum_{i=1}^{n}{i \cdot F\left( i \right)} i=1niF(i)

思路:

因为所有数据范围 ≤ 2 e 4 \leq2e^4 2e4,可以预处理出每个数的合约数,用 v e c t o r vector vector存储,再树上启发式合并统计
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=2e4+5;
const ll mod=1e9+7;
int tot,prim[maxn],book[maxn],siz[maxn],dfn[maxn],tmp[maxn];
int n,rt,t,num,son[maxn],id[maxn],w[maxn],fd[maxn];
ll ans,sum;
vector<int>mp[maxn],p[maxn];
void init(){
    for(int i=2;i<=maxn;i++){
    	if(!book[i])prim[tot++]=i;
    	for(int j=0;j<tot&&prim[j]*i<maxn;j++){
    		book[i*prim[j]]=true;
    		if(i%prim[j]==0)break;
		}
	} 
	for(int i=1;i<=maxn;i++){
		for(int j=1;j*j<=i;j++){
			if(i%j==0){
				if(book[j])p[i].push_back(j);
				if(j!=i/j&&book[i/j])p[i].push_back(i/j);
			}
		}
	}
}
void dfs1(int x,int fa){
	siz[x]=1;
	dfn[x]=++num;
	id[num]=x;
	for(auto v:mp[x]){
		if (v==fa)continue;
		dfs1(v,x);
		siz[x]+=siz[v];
		if (siz[v]>siz[son[x]])son[x]=v;
	}
}
void dfs(int x,int f,int op){
	for(int i = 0; i < mp[x].size(); i++) {
        int y = mp[x][i];
		if(y!=f&&y!=son[x])dfs(y,x,0);
	}
	if(son[x])dfs(son[x],x,1);
	for(int i = 0; i < mp[x].size(); i++) {
        int v = mp[x][i];
		if(v==f||v==son[x])continue;
		for(int j=dfn[v];j<=dfn[v]+siz[v]-1;j++){
			int y=id[j];
			tmp[w[y]]++;
		}
	}
	tmp[w[x]]++;
	for(auto h:p[w[x]])
		fd[x]+=tmp[h];
	if(!op){
		for(int j=dfn[x];j<=dfn[x]+siz[x]-1;j++){
			int y=id[j];
			tmp[w[y]]--;
		}
	}
}
int main(){
    init();
    scanf("%d",&t);
    while(t--){
    	ans=num=0;
    	scanf("%d%d",&n,&rt);
    	memset(fd,0,sizeof(fd));
    	memset(son,0,sizeof(son));
    	for(int i=1;i<=n;i++)mp[i].clear();
        for(int i=1,x,y;i<n;i++){
    	    scanf("%d%d",&x,&y);
    	    mp[x].push_back(y);
    	    mp[y].push_back(x);
	    }
	    for(int i=1;i<=n;i++)
    	    scanf("%d",&w[i]);
        dfs1(rt,0);
        dfs(rt,0,0);
        for(int i=1;i<=n;i++){
    	    ans+=1ll*i*fd[i];
	    }
        printf("%lld\n",ans%mod);
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值