【牛客网】埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛 B.合约数

题目链接

(树+dfs+素数筛)

题意:给定一棵n个节点的树,并且根节点的编号为p,第i个节点有属性值vali, 定义F(i): 在以i为根的子树中,属性值是vali的合约数的节点个数。y 是 x 的合约数是指 y 是合数且 y 是 x 的约数。小埃想知道对1e9+7取模后的结果.(注意:合数是指非质数的数,约数即是因子)

题解:首先如果正常遍历每个结点的子树可以得到对应的f[i]值,而对ans的贡献值为i*f[i],可是这样不断重复的遍历明显就会TLE,而且其中很多地方都重复算了。那么我们换个角度思考,当遍历到一个节点时,我们知道他对是他的倍数的根结点有f[rt](这里假定rt为他那个根)加1的贡献,即有对ans加rt。因此我们只需要从根开始遍历这棵树,每遍历到一个结点v,对它的vail值的每个合约数x的cnt[x]都加v,如果后面出现了这个因子,那么ans加上他的cnt[vail]时就得到了之前事先预处理的v值,即子结点是根结点的合约数时的贡献。不过在回溯时,要注意减去他对他所有合约数的预处理值(因为预处理的值是只有当前结点的子数能使用的贡献点)不理解的结合代码理解吧~还有由于题目数据vail的范围是1~1e4,我们可以使用素数筛预处理出每个数的所有合约数。

代码如下:

#include<iostream>  
#include<cstring>  
#include<string>  
#include<cstdio>  
#include<cmath>  
#include<vector>  
#include<queue>  
#include<map>  
#include<algorithm>  
using namespace std;
#define inf 0x3f3f3f3f  
#define ll long long  
#define mod (ll)(1e9+7)  
const int maxn = 2e4 + 500;
vector<int>h[maxn];//合约数  
bool impri[maxn];//合数  
vector<int>tre[maxn];
ll cnt[maxn];
ll a[maxn];
ll ans;
void fun() {//先打表得出1e4内所有数的合约数  
	for (int i = 2; i <= (int)1e4; i++) { //素数塞  
		if (!impri[i]) {
			for (int j = i + i; j <= (int)1e4; j += i)
				impri[j] = true;
		}
	}
	for (int i = 4; i <= (int)1e4; i++) {//打表得到合约数  
		if (!impri[i])continue;//素数  
		for (int j = i; j <= (int)1e4; j += i)
			h[j].push_back(i);//约数  
	}
}
void dfs(int v, int pre)
{
	//先预处理当前结点所有合约数出现时当前节点对ans的贡献  
	for (int i = 0; i < h[a[v]].size(); i++)
		cnt[h[a[v]][i]] = (cnt[h[a[v]][i]] + v) % mod;

	//在当前结点对之前的祖先结点预处理的贡献值取出加到ans上  
	ans = (ans + cnt[a[v]]) % mod;

	for (int i = 0; i < tre[v].size(); i++)
	{
		if (tre[v][i] == pre)continue;
		dfs(tre[v][i], v);
	}

	//回溯前需要减去当前结点对后代子孙结点的贡献,因为回溯回父结点不属于其子孙  
	for (int i = 0; i < h[a[v]].size(); i++)
		cnt[h[a[v]][i]] = (cnt[h[a[v]][i]] + mod - v) % mod;
}
int main()
{
	fun();
	int t;
	cin >> t;
	while (t--)
	{
		int n, p, u, v;
		scanf("%d%d", &n, &p);
		for (int i = 1; i <= n; i++)
			tre[i].clear();
		for (int i = 0; i < n - 1; i++)
		{
			scanf("%d%d", &u, &v);
			tre[u].push_back(v);//不能只存单向边,因为我们不清楚哪个是父结点  
			tre[v].push_back(u);//遍历的时候跳过父结点即可  
		}
		for (int i = 1; i <= n; i++)
			scanf("%lld", &a[i]);
		ans = 0;
		dfs(p, -1);
		printf("%lld\n", ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41156591/article/details/80208176