挑战程序设计竞赛 4.1.3 计数
题意: 给出一个数n, n ≤ 1e9, 问有多少个数 a, 1 ≤ a < n, 使得不存在 x > 1, y > 0, z > 0, n = xy, a = xz.
思路: 实际上问比 n 小且与 n 互质的数的个数, 求欧拉函数.
view code
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int n;
const int maxnum = 40005;
int prim[maxnum], pvis[maxnum + 5], pcnt;
void getprim() {
for (int i = 2; i <= maxnum; i++) {
if (!pvis[i]) prim[++pcnt] = i;
for (int j = 1; j <= pcnt && prim[j] * i <= maxnum; j++) {
pvis[prim[j] * i] = 1;
if (i % prim[j] == 0) break;
}
}
}
int main() {
getprim();
while (scanf("%d", &n) != EOF && n) {
if (n == 1) {
printf("0\n");
continue;
}
int res = n;
for (int i = 1; i <= pcnt && n > 1; i++) {
if (n % prim[i] == 0) {
res = res / prim[i] * (prim[i] - 1);
while (n % prim[i] == 0) n /= prim[i];
}
}
if (n > 1) res = res / n * (n - 1);
printf("%d\n", res);
}
}
题意: 有一串 n 个珠子的项链, 每个珠子的颜色有 3 种, 经过旋转和轴对称变换后相等的珠子是视为相同的, 问有多少种项链.
思路: 旋转操作有[0, n - 1] 共 n 种操作, 对称操作分奇偶讨论, 然后用Pólya 定理计算结果.
view code
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
int n;
int prim[10] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
inline ll ksm(ll a, ll n) {
ll r = 1;
while (n) {
if (n & 1) r = r * a;
a = a * a;
n >>= 1;
}
return r;
}
int gcd(int x, int y) {
if (y == 0) return x;
return gcd(y, x % y);
}
int main() {
while (scanf("%d", &n) != EOF && n != -1) {
if (n == 0) {
printf("0\n");
continue;
}
ll res = ksm(3LL, (ll)n); // 0
for (int i = 1; i < n; i++) {
res += ksm(3LL, (ll)gcd(i, n));
}
if (n & 1)
res += n * ksm(3LL, (n + 1) / 2);
else
res += 2 * n * ksm(3LL, n / 2);
printf("%lld\n", res / 2 / n);
}
}
题意: 有一串 s 个珠子的项链, 每个珠子的颜色有 c 种, 经过旋转和轴对称变换后相等的珠子是视为相同的, 问有多少种项链.
思路: 旋转次数有[0, s - 1] 共 s 种, 对称操作分奇偶讨论, 不过都是 n 种, 然后用 Pólya 定理计算结果.
view code
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
int c, s;
inline ll ksm(ll a, ll n) {
ll r = 1;
while (n) {
if (n & 1) r = r * a;
a = a * a;
n >>= 1;
}
return r;
}
int gcd(int x, int y) {
if (y == 0) return x;
return gcd(y, x % y);
}
int main() {
while (scanf("%d %d", &c, &s) != EOF && c) {
ll res = ksm((ll)c, (ll)s); // 0
for (int i = 1; i < s; i++) {
res += ksm((ll)c, (ll)gcd(i, s));
}
if (s & 1)
res += s * ksm((ll)c, (s + 1) / 2);
else
res += (c + 1) * s / 2 * ksm((ll)c, s / 2);
printf("%lld\n", res / 2 / s);
}
}
题意: 有两国的大使共 N 人要坐在一个圆周上开会, 不能有连续的 k 个同国的人坐在一起, 问有多少种坐法.
思路: 还是 Pólya 定理的模型, 难点在于确定了一个周期之后, 有多少种合法的坐法. 对于一个循环节, 不妨考虑开头为A, 根据对称性最后把答案乘 2 就是总的情况数. 定义dp_a[i][j], dp_b[i][j]为总长为 i, 结尾有 j 个 A/B, 中间没有连续 k 个同国 (但不考虑首尾相连超过 k, 不然状态数有\(N^3\)种) 的情况数. 需要计算一个合法的循环节时, 分类讨论结尾是A/B: 结尾是A时, 考虑除去开头和结尾后还剩的串长(保证不为0, 否则会重复计数), 此时剩下的串开头和结尾都是B, 它的数量和同长度的开头结尾都是A的串相同! 再乘以出现该剩余串长的次数; 结尾是 B 时, 首尾相连必不连续, dp_b的前缀和即是所求. 最后还有一个特意省略的情况: 全为A, 注意此时不能简单地认为首尾数相加不大于 k 即可, 因为此时整个环都是 A 应该比较 n 与 k.
view code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inc(i, l, r) for (int i = l; i <= r; i++)
const int maxn = 1005;
const int mod = 1000003;
int n, k;
int dp_a[maxn][maxn], dp_b[maxn][maxn]; //一定以a开头,总长为i, 结尾是j个a/b
int sum_a[maxn], sum_b[maxn];
inline ll ksm(ll a, ll n) {
ll r = 1;
while (n) {
if (n & 1) r = r * a % mod;
a = a * a % mod;
n >>= 1;
}
return r;
}
int main() {
while (cin >> n >> k && n) {
memset(dp_a, 0, sizeof(dp_a));
memset(dp_b, 0, sizeof(dp_b));
dp_a[1][1] = 1;
inc(i, 1, n - 1) {
inc(j, 1, min(i, k)) {
if (j + 1 <= k)
dp_a[i + 1][j + 1] =
(dp_a[i + 1][j + 1] + dp_a[i][j]) % mod;
dp_b[i + 1][1] = (dp_b[i + 1][1] + dp_a[i][j]) % mod;
}
inc(j, 1, min(i - 1, k)) {
dp_a[i + 1][1] = (dp_a[i + 1][1] + dp_b[i][j]) % mod;
if (j + 1 <= k)
dp_b[i + 1][j + 1] =
(dp_b[i + 1][j + 1] + dp_b[i][j]) % mod;
}
}
memset(sum_a, 0, sizeof(sum_a));
memset(sum_b, 0, sizeof(sum_b));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= min(i, k); j++) {
sum_a[i] = (sum_a[i] + dp_a[i][j]) % mod;
sum_b[i] = (sum_b[i] + dp_b[i][j]) % mod;
}
}
ll res = 0;
for (int i = 1; i <= n; i++) {
int l = __gcd(i, n);
//循环节长度
for (int j = 2; j <= min(k, l - 1); j++) {
//首尾a之和
res = (res + (j - 1) * sum_a[l - j]) % mod;
}
//结尾b
res = (res + sum_b[l]) % mod;
if (k >= n) res++; //全a
}
printf("%lld\n", res * 2 * ksm((ll)n, mod - 2LL) % mod);
}
}

浙公网安备 33010602011771号