挑战程序设计竞赛 4.1.3 计数

 

POJ 2407

题意: 给出一个数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);
    }
}

 

POJ 1286

题意: 有一串 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);
    }
}

 

POJ 2409

题意: 有一串 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);
    }
}

 

AOJ 2164

题意: 有两国的大使共 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);
    }
}
posted @ 2020-03-01 11:46  Linqi05  阅读(2)  评论(0)    收藏  举报