AtCoder Regular Contest 106 DEF

ARC106

D - Powers

用二项式定理展开,把同类项合并到一起,差不多就是这样:

把两个括号内分别计算再相乘

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2e5 + 5, mod = 998244353;
int n, m, a[N], b[N], pw[350], in[350], sum[305];
int qpow (int x, int y) {
    int t = 1;
    while (y) {
        if (y & 1) t = t * x % mod;
        x = x * x % mod, y >>= 1;
    } return t;
}
signed main() {
    read (n), read (m); pw[0] = in[0] = 1;
    for (int i = 1; i <= n; ++i) read (a[i]);
    for (int i = 1; i <= m; ++i)
        pw[i] = pw[i - 1] * i % mod, in[i] = qpow (pw[i], mod - 2);
    for (int i = 1; i <= n; ++i)
        for (int j = 0, t = 1; j <= m; ++j, t = t * a[i] % mod)
            sum[j] = (sum[j] + t * in[j]) % mod;
    for (int i = 1; i <= n; ++i) a[i] += a[i], b[i] = a[i];
    int inv = qpow (2, mod - 2);
    for (int i = 1; i <= m; ++i) {
        int res = 0;
        for (int j = 0; j <= i; ++j)
            res = (res + sum[j] * sum[i - j]) % mod;
        res *= pw[i]; for (int j = 1; j <= n; ++j) res -= a[j]; res = res % mod + mod;
        printf ("%lld
", res * inv % mod);
        for (int j = 1; j <= n; ++j) a[j] = a[j] * b[j] % mod;
    }
    return 0;
}

E - Medals

容易想到二分答案,可以得出答案上界是 (2 imes n imes k)

判定可行的方式比较奇怪,用到了 Hall定理 ,把每个人得到每块奖牌看作二分图的左部点,共 (n imes k) 个,把每一天看作右部点。如果第 (i) 个人在第 (j) 天上班,把 (i)(k) 个点都和 (j) 连一条边

但边数很多,不能直接搞。根据 (hall) 定理,左部点的每个子集 (A) 连向的右部点子集的大小 (P(A)),要满足 (| A|leq|P(A)|)。先考虑同一个人 (i)(k) 个点,这些点连向的点集 (S(i))相同,只要判断一下 (kleq |S(i)|),然后可以把 (k) 个点看成一个

不同的人组成的点集,共有 (2^n) 种情况。把每一天哪些人来上班用二进制表示。如果某一天的上班情况用二进制表示为 (Q)(Q_i) 表示二进制下第 (i) 位(第 (i) 个人上班否)。(Q) 对哪些组合有贡献呢?显然是二进制表示下和 (Q) 有重合的点集。但这个似乎不好算,可以反过来,把所有点集 (+1),然后没有重合的 (-1),用 (fwt) 计算 (-1) 的过程

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 18, M = 1e5 + 5;
int n, k, res, d[N], a[1 << N], c[1 << N], p[2 * N * M];
int check (int t) {
    memset (a, 0, sizeof (a));
    for (int i = 1; i <= t; ++i) ++a[p[i]];
    int mx = 1 << n;
    for (int i = 1; i < mx; i <<= 1)
        for (int j = 0; j < mx; j += (i << 1))
            for (int k = 0; k < i; ++k) a[j | k] += a[j | k | i];
    for (int i = mx - 1; i >= 1; --i)
        if (c[i] * k > t - a[i]) return 0; return 1;
}
signed main() {
    read (n), read (k);
    for (int i = 1; i <= n; ++i) read (d[i]);
    c[0] = 0;
    for (int i = 1; i < (1 << n); ++i)
        c[i] = c[i >> 1] + (i & 1);
    for (int i = 1, qwq; i <= 2 * n * k; ++i) {
        p[i] = (1 << n) - 1;
        for (int j = 1; j <= n; ++j) {
            qwq = i % (d[j] << 1);
            if (qwq && qwq <= d[j]) p[i] ^= (1 << j - 1);
        }
    }
    int l = 1, r = 2 * n * k, res = r;
    while (l <= r) {
        int mid = l + r >> 1;
        if (check (mid)) res = mid, r = mid - 1;
        else l = mid + 1;
    }
    return printf ("%lld
", res), 0;
}

F - Figures

思路巧妙但容易理解的计数问题。

首先,要保证图连通,不难发现,充要条件是每个 (Part) 都至少使用一个接口。先把这些接口都选了,这是答案的第一部分

然后,一共使用 (2n-2) 个接口,还剩 (n-2) 个要选,奇妙的事情发生了

把剩下的所有接口放在一起,假设一共 (S) 个,从中选 (n-2) 个的排列数就是答案的第二部分。(But...Why???)

其实就是要证明一个排列和一种接法一一对应(这里的排列包括第一部分从每个 (Part) 选的一个接口)对应方法:从左向右扫描整个排列,如果当前接口已经标记过,跳过;否则,从右边选一个最近的在不同 Part 上的接口作为边的另一端。这样就和接法对应上了,并且可以发现改变排列的顺序也会改变连边方案。

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2e5 + 5, mod = 998244353;
int n, s, res = 1;
signed main() {
    read (n);
    for (int i = 1, x; i <= n; ++i)
        read (x), res = res * x % mod, s += x - 1;
    s %= mod;
    for (int i = 1; i <= n - 2; ++i)
        res = res * (s - i + 1) % mod;
    return printf ("%lld
", res), 0;
}
原文地址:https://www.cnblogs.com/whx666/p/arc106.html