HDU 5514Frogs

3 篇文章 0 订阅
1 篇文章 0 订阅

原题传送门

题意:

有n只青蛙,m块石头呈环放置(从0~m-1标记),第i只青蛙每次跳ai步,跳跃次数无限制。求被n只青蛙踩过的石头的下表和

思路1(欧拉函数):

很明显,对于第i只青蛙,被踩的石头的编号一定是gcd(ai, m)的倍数,那么我们先将ai转换为gcd(ai,m)
然后对于第 j 块石头,我们定义它只能被跳跃步数为x的青蛙踩中(x满足gcd(m,j)= x)例如下面的样例:

2 12
9 10
那么这个样例之中被踩过并且有贡献的石头编号为2,3,4,6,8,9,10
按照我们的定义
2 10 是被跳跃步数为2的青蛙踩过
3 9 是被跳跃步数为3的青蛙踩过
4 8 是被跳跃步数为4的青蛙踩过
6 是被跳跃步数为6的青蛙踩过

这里的青蛙只有两个,但是我们可以造两个步数为4 和 6的青蛙,这对答案是完全没有影响的
因此 x 的答案贡献为 m以内 j [gcd(m,j)== x]
由gcd(m,j)= x,可以得到gcd(m/x,j/x)= 1,即(j/x)为(m/x)内所有与(m/x)互素的数
那么上面对于x的答案贡献就转换为 x * i [gcd(m/x,i)== 1]

知识点:设小于n的所有与n互质的数的和为Sum,Sum=n∗φ(n)/2

证明:
1. gcd(x,n)= 1,那么gcd(n-x,n)= 1同样满足
2. 可见n以内与n互素的数字都是成对出现的,而素数对的个数就是φ(n)/2,并且每一对素数和为n
证毕

由此x的答案再次变换为

x *(φ(m/x)/ 2 *(m/x))

稍稍化简得到

φ(m/x)/ 2*m

到这里就可以直接枚举m的因子,看跳跃步数为当前的因子fac青蛙能否被构造(fac % ai == 0)即可

AC代码1:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 9;

int a[N], fac[N];

int Euler (int n) {
	ll res = n;
	for (int i = 2; i * i <= n; i++) {
		if (n % i == 0) {
			res = res / i * (i - 1);
			while (n % i == 0) {
				n /= i;
			}
		}
	}
	if (n > 1) {
		res = res / n * (n - 1);
	}
	return res;
}

int get_fac(int n) {
	int len = 0;
	for (int i = 2; i * i <= n; i ++) {
		if (n % i == 0) {
			fac[len++] = i;
			if (i * i != n) {
				fac[len++] = n / i;
			}
		}
	}
	sort (fac, fac + len);
	return len;
}

int main() {
	int T, cnt = 1;
	scanf ("%d", &T);
	while (T --) {
		int n, m;
		bool flag = false;
		scanf ("%d%d", &n, &m);
		int len = get_fac(m);
		for (int i = 0; i < n; i ++) {
			scanf ("%d", a + i);
			a[i] = __gcd(a[i], m);
			if (a[i] == 1) {
				flag = true;
			}
		}
		ll ans = 0;
		for (int i = 0; i < len; i ++) {
			for (int j = 0; j < n; j ++) {
				if (fac[i] % a[j] == 0) {
					ans += 1ll * Euler(m/fac[i]) * m / 2 ;
					break;
				}
			}
		}
		printf ("Case #%d: ", cnt ++);
		if (flag) {
			cout << 1ll * m * (m - 1) / 2 << endl;
		} else {
			cout << ans << endl;
		}
	}
	return 0;
}

思路2(容斥原理):

首先预处理出m的所有因子,每个因子的答案贡献次数初始化为1,然后根据容斥原理去调节后续每个因子的答案贡献次数。例如下面的样例:

2 24
9 10
24的所有因子为 2,3,4,6,8,12

初始化所有因子的答案贡献为1
计算2的答案贡献的时候,2的答案贡献次数为1,因此4,6,8,12的答案贡献次数 -1
计算3的答案贡献的时候,3的答案贡献次数为1,6和12的答案贡献次数-1
计算4的答案贡献的时候,4的答案贡献次数为1,8和12的答案贡献次数-1
(!高潮来了!)
计算6的答案贡献的时候,答案贡献次数为-1,这个时候12的答案贡献次数 -(-1)也就是+1
计算8的答案贡献的时候,答案贡献次数为-1,后续因子没有8的倍数,因此无须调节其他因子的答案贡献次数
计算12的答案贡献的时候,答案贡献次数为-1,与上面同理
(容斥原理真奇妙)

AC代码2:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 9;

int a[N], vis[N], fac[N];

int init(int n) {
	memset (vis, 0, sizeof vis);
	int len = 0;
	for (int i = 2; i * i <= n; i ++) {
		if (n % i == 0) {
			fac[len++] = i;
			if (i * i != n) {
				fac[len++] = n / i;
			}
		}
	}
	sort (fac, fac + len);
	return len;
}

int main() {
	int T, cnt = 1;
	scanf ("%d", &T);
	while (T --) {
		int n, m;
		bool flag = false;
		scanf ("%d%d", &n, &m);
		int len = init(m);
		for (int i = 0; i < n; i ++) {
			scanf ("%d", a + i);
			a[i] = __gcd(a[i], m);
			if (a[i] == 1) {
				flag = true;
			}
			for (int j = 0; j < len; j ++) {
				if (fac[j] % a[i] == 0) {
					vis[j] = 1;
				}
			}
		}
		ll ans = 0;
		for (int i = 0; i < len; i ++) {
			ll res = m / fac[i];
			ans += res * (res - 1) / 2 * fac[i] * vis[i];
			for (int j = i + 1; j < len; j ++) {
				if (fac[j] % fac[i] == 0) {
					vis[j] -= vis[i];
				}
			}
		}

		printf ("Case #%d: ", cnt ++);
		if (flag) {
			cout << 1ll * m * (m - 1) / 2 << endl;
		} else {
			cout << ans << endl;
		}
	}
	return 0;
}

(PS.以上的答案贡献计算都是O(1)的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值