poj2409 Let it Bead

16 篇文章 0 订阅

题目链接

题意:

给我们 m m m种不同颜色的珠子,每种颜色的珠子数量无限。现在我们要从中挑 n n n个珠子,穿成一个环形手链,问能够制造多少种不同的手链。如果两个手链经旋转或翻转后能够完全重合在一起,对应位置的珠子颜色完全相同,则视为同一种手链。

题解:

这道题目是 P o l y a Polya Polya定理的模板题。

对于这道题目,首先我们要考虑有几种置换方式。不难发现有两种,分别是旋转和翻转。

先分析旋转置换。

我们可以发现,根据一个珠子旋转的次数,这种置换方式总共有 n n n种情况,也就是一个珠子旋转 k k k次,其中 k = 0 , 1 , 2 , … , n − 1 k=0,1,2,\dots,n-1 k=0,1,2,,n1。对于每种旋转,我们要分析总共有几个循环置换。
要想达到循环,必须要满足 x + k t ≡ x ( m o d   n ) x+kt\equiv x(mod\ n) x+ktx(mod n),其中 x x x就是要旋转的珠子的起始位置。其实也就是求这个正整数 t t t的最小取值,即对于旋转 k k k来说,最少经过几次旋转能够回到起始位置,也就是完成一次循环。上面的同余式可以转化为 k t ≡ 0 ( m o d   n ) kt\equiv0(mod\ n) kt0(mod n),再化简成 k t + n r = 0 kt+nr=0 kt+nr=0,可以知道这个 t t t的最小取值就是 n g c d ( n , k ) \frac n{gcd(n,k)} gcd(n,k)n。现在求出了这个旋转的循环节长度,下面我们还要求出, n n n个珠子总共有多少这样的循环,这个比较容易求,就是 n n g c d ( n , k ) = g c d ( n , k ) \frac n{\frac n{gcd(n,k)}}=gcd(n,k) gcd(n,k)nn=gcd(n,k)。因此,对于旋转置换来说,其不动点的个数就是 ∑ k = 0 n − 1 m g c d ( n , k ) \sum_{k=0}^{n-1}m^{gcd(n,k)} k=0n1mgcd(n,k)

再分析翻转置换。

对于翻转置换,我们要考虑 n n n的奇偶性。
n n n是奇数时,只有这么一种翻转方式:
在这里插入图片描述
对于这种翻转方式,我们容易求出,其包含的循环置换个数总共有 n + 1 2 \frac {n+1}2 2n+1。因此对于奇数情况,不动点的个数有 n ∗ m n + 1 2 n*m^{\frac {n+1}2} nm2n+1

n n n是偶数时,就存在两种翻转方式。首先第一种就是:
在这里插入图片描述
对于这种情况,循环置换的个数,有 n 2 + 1 \frac n2+1 2n+1个。此时的不动点个数就是 n 2 ∗ m n 2 + 1 \frac n2*m^{\frac n2+1} 2nm2n+1个。

第二种翻转方式是:
在这里插入图片描述
对于这种翻转方式,对应的不动点个数是 n 2 ∗ m n 2 \frac n2*m^{\frac n2} 2nm2n

因此所有情况的总的不动点个数就是以上情况的不动点个数之和。对应的平均数就是总的不动点个数除以 2 n 2n 2n即可。

实现细节见代码:

#include <bits/stdc++.h>
using namespace std;
int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}
int qpow(int a, int b) {
	int ans = 1;
	while (b) {
		if (b & 1) {
			ans = ans * a;
		}
		a = a * a;
		b >>= 1;
	}
	return ans;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int m, n;
	while (cin >> m >> n && (m || n)) {
		int sum = 0;
		int ans = 0;
		for (int i = 0; i < n; i++) { // 旋转置换的不动点个数
			int d = gcd(i, n);
			ans += qpow(m, d);
		}
		// 翻转置换的不动点个数
		if (n & 1) {
			ans += qpow(m, (n + 1) / 2) * n;
		}
		else {
			ans += qpow(m, n / 2 + 1) * n / 2 + qpow(m, n / 2) * n / 2;
		}
		cout << ans / (2 * n) << endl; // 求平均数
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值