HDU 5468 Puzzled Elena (2015年上海赛区网络赛A题)

1.题目描述:点击打开链接

2.解题思路:本题利用dfs序+容斥原理+前缀和性质解决。题目中要求每个结点,和多少个它的子结点互素。如果每次为了求一个点去跑一遍dfs,复杂度将是O(N(N+M))。一定会超时。因此需要深入加以分析。注意到n的范围是10^5以内的,因此可以事先用线性筛求出每个数含有哪些素因子。接下来,我们尝试利用dfs序来求解。设num[i]表示遍历到当前结点时候,含有因数i(注意,不一定是素数)的结点个数。可以发现,如果第一次遍历到结点u,含有u的因数的个数为a,那么第二次遍历到u时候,含有u的因数的个数变成了b,那么b-a就是u的子树中,含有u的因数的结点个数,即和u不互素的结点个数。用总的结点数减掉这部分,即得到了和u互素的结点个数。这正是用了前缀和的性质。

那么,如何求解有当前有多个结点含有u的因数呢?可以利用容斥原理求解。因为我们已经预处理出来了所有数的素因数,假设有len个素因数,由于“含有”即表示只要有1个即可。因此结果就是{只含有1种素因子的个数}-{只含有2种素因子的个数}+{只含有3个素因子的个数}-...+(-1)^(n-1){含有n个素因子的个数}。这恰好就是容斥原理。至此,问题得以解决。

3.代码:

#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<list>
#include<complex>
#include<functional>
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
#define pb push_back
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int,int> P;


const int N = 100000 + 10;
vector<int>g[N];
int num[N];
int val[N];
int L[N], R[N], ans[N];
struct Edge
{
	int to, next;
}edge[N << 2];
int head[N];
int e, n;

void init()
{
	for (int i = 2; i<N; i++)
	{
		if (!g[i].empty())continue;
		for (int j = i; j<N; j += i)
			g[j].pb(i);
	}
}

void addedge(int u, int v)
{
	edge[e].to = v;
	edge[e].next = head[u];
	head[u] = e++;
}

int bitcount(int x) { return !x ? 0 : bitcount(x / 2) + (x & 1); }

int calc(int x, int val)//计算当前有多少个结点含有x的因数,val=0表示不更新num数组,val=1表示用x来更新num数组
{
	int len = g[x].size();
	int res = 0;
	for (int i = 1; i<1 << len; i++)
	{
		int t = 1;
		for (int j = 0; j<len; j++)
			if (i >> j & 1)t *= g[x][j];
		if (bitcount(i) & 1)res += num[t];
		else res -= num[t];
		num[t] += val;
	}
	return res;
}

int dfs(int u, int fa)//dfs返回的是u这棵子树的总结点数
{
	int cnt = 0;
	L[u] = calc(val[u], 0);
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v != fa)
			cnt += dfs(v, u);
	}
	R[u] = calc(val[u], 1);
	ans[u] = cnt - (R[u] - L[u]);
	if (val[u] == 1)ans[u]++; //注意!1和自身也互素
	return cnt + 1;
}

int main()
{
	init();
	int n;
	int kase = 0;
	while (~scanf("%d", &n))
	{
		e = 0;
		memset(head, -1, sizeof(head));
		me(num);
		int u, v;
		for (int i = 1; i<n; i++)
		{
			scanf("%d%d", &u, &v);
			u--, v--;
			addedge(u, v);
			addedge(v, u);
		}
		for (int i = 0; i<n; i++)
			scanf("%d", &val[i]);
		dfs(0, -1);
		printf("Case #%d:", ++kase);
		for (int i = 0; i<n; i++)
			printf(" %d", ans[i]);
		puts("");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值