AtCoder Grand Contest 005 CDEF

AGC005

C - Tree Restoring

看到“树上最远点距离”这种东西,直径应该就有一些性质

把直径抽出来,直径中点的数值是最小的,当然可能有 (2) 个,但如果 (>2) 个就没戏了

然后直径的两端一定是对称且从中间向两边每次 (+1),如果从最小值到最大值某个值出现的次数 (<2) 也没戏了

还有一个判断是最大值与最小值之间的关系,如果中点在点上,需满足 (max=2 imes min),否则需要判断 (max = 2 imes min - 1)

以上条件都满足一定是可以构造出来的

#include <bits/stdc++.h>
using namespace std;
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 = 105;
int n, mn, mx, a[N], c[N];
#define fail return puts ("Impossible"), 0
#define success return puts ("Possible"), 0
signed main() {
    read (n); mn = n;
    for (int i = 1; i <= n; ++i) read (a[i]), ++c[a[i]];
    for (int i = 1; i <= n; ++i)
        mn = min (a[i], mn), mx = max (a[i], mx);
    if (c[mn] > 2) fail;
    int mm = c[mn] == 1 ? mn * 2 : mn * 2 - 1;
    if (mx != mm) fail;
    for (int i = mn + 1; i <= mx; ++i)
        if (c[i] < 2) fail;
    success;
    return 0;
}

D - ~K Perm Counting

以前写过了,复制过来

key:容斥,把相互关联的数串成链,在链上dp

dp算出有至少 (k) 个不合法的方案进行容斥

在原序列上很难dp,把一些关联的数串成链

一个数 (x) 不可以填在 (x+k),也不可以填在 (x-k),就把这样一些数串起来,一个位置一个数值一个位置一个数值....

中间的边选了一条就代表一个不合法,不能选相邻两条边

这样每条链没有重复的部分。在每条链开头打上标记就可以拼接起来处理了。然后就是简单dp

(f_{i,j,k}) 表示前 (i) 个,选了 (j) 条,最后一条有没有选,在链头需要特判

#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 = 2005, mod = 924844033;
int n, k, res, cnt, f[N << 1][N][2], pw[N], is[N << 1];
signed main() {
    read (n), read (k); pw[0] = 1;
    for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * i % mod;
    for (int i = 1; i <= k; ++i) {
        for (int t = 0; t < 2; ++t)
            for (int j = i; j <= n; j += k) is[++cnt] = (i != j);
    } f[0][0][0] = 1;
    for (int i = 1; i <= cnt; ++i)
        for (int j = 0; j <= n; ++j) {
            f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]) % mod;
            if (is[i] && j) f[i][j][1] = f[i - 1][j - 1][0];
        }
    for (int i = 0, t = 1; i <= n; ++i, t = -t)
        (res += t * (f[cnt][i][0] + f[cnt][i][1]) * pw[n - i]) %= mod;
    return printf ("%lld
", (res + mod) % mod  ), 0;
}

E - Sugigma: The Showdown

如果步数有限:从根节点往下拓展,枚举最后停留在每一个点的答案。如果最后能停在 (x) 点,从根到 (x) 的路径都要能走通(比追赶着先到),这个好办,如果被抓了直接 (return),不必再处理子树

无限的情况:无限步数一定是在“耍猴”了,就是在 (2) 个相邻点之间来回跳,但追赶者的树上这两个点的距离 (>2),永远也追不到。但前提是到达“耍猴点”之前没有被抓住

#include <bits/stdc++.h>
using namespace std;
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, M = N << 1;
int n, x, y, res, d[N], fa[N];
vector<int> gx[N], gy[N];
#define pb push_back
void dfs (int u, int la) {
    d[u] = d[la] + 1, fa[u] = la;
    for (int v : gy[u]) if (v != la) dfs (v, u);
}
int dist (int x, int y) {
    int num = 0;
    if (d[x] < d[y]) swap (x, y);
    if (d[x] - d[y] > 2) return 1;
    while (d[x] != d[y]) ++num, x = fa[x];
    while (x != y) {
        x = fa[x], y = fa[y];
        if ((num += 2) > 2) return 1;
    }
    return 0;
}
void dfs (int u, int la, int dp) {
    if (dp >= d[u]) return; res = max (res, d[u]);
    for (int v : gx[u]) {
        if (v == la) continue;
        if (dist (u, v)) { puts ("-1"); exit (0); }
        dfs (v, u, dp + 1);
    }
}
signed main() {
    read (n), read (x), read (y);
    for (int i = 1, u, v; i < n; ++i)
        read (u), read (v), gx[u].pb (v), gx[v].pb (u);
    for (int i = 1, u, v; i < n; ++i)
        read (u), read (v), gy[u].pb (v), gy[v].pb (u);
    d[0] = -1; dfs (y, 0); dfs (x, 0, 0);
    return printf ("%d
", res << 1), 0;
}

F - Many Easy Problems

先进行一点点的转换,每个连通块都是一棵子树,而树中 (num(点)=num(边)+1)。因为树上每条边断开都能把树分为两块,所以边往往具有更奇妙的性质

对于每一个点集,如果一条边被取,当且仅当这条边的左右两部分都有点在集合中

然后就好办了,对每一条边考虑,有 (f(s)=C_{n}^{s}+sumlimits_{i=1}^{m}C_{n}^{s}-C_{x_i}^{s}-C_{y_i}^{s}),啥意思呢?(x_i,y_i) 表示把边 (i) 断开后两部分的大小,就是用所有情况减去只在某一边有点的情况。第一个 (C_{n}^{s}) 就是 (num(点)=num(边)+1)(1)

接下来展开

(f(s)=C_{n}^{s}+(n-1)C_{n}^{s}-sumlimits_{i=s}^{n}cnt_i imes C_{n}^{i})(cnt_i) 表示 (x_i,y_i) 中数值为 (i) 的数量

就展开:

(f(s)=nC_{n}^{s}-sumlimits_{i=s}^{n}frac{cnt_i imes i!}{s! imes (i-s)!}=nC_{n}^{s}-frac{1}{s!}sumlimits_{i=s}^{n}frac{cnt_i imes i!}{(i-s)!})

好家伙,把 (cnt_i imes i!) 设为 (A_i)((i-s)!) 设为 (B_i),这不是个 (ntt) 的板子!

#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, M = N << 1, mod = 924844033;
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;
}
int n, cnt, h[N], nxt[M], to[M], cc[N], sz[N];
void add (int u, int v) {
    to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
void dfs (int u, int la) {
    sz[u] = 1;
    for (int i = h[u], v; i; i = nxt[i])
        if ((v = to[i]) != la) dfs (v, u), sz[u] += sz[v];
    if (u != 1) ++cc[sz[u]], ++cc[n - sz[u]];
}
int pw[N], in[N];
int C (int x, int y) {
    return pw[x] * in[y] % mod * in[x - y] % mod;
}
int lim = 1, len, rev[N << 2], a[N << 2], b[N << 2];
void ntt (int *a, int opt) {
    for (int i = 0; i < lim; ++i)
        if (i < rev[i]) swap (a[i], a[rev[i]]);
    for (int m = 1; m < lim; m <<= 1) {
        int tmp = qpow (5, (mod - 1) / (m * 2));
        if (opt == -1) tmp = qpow (tmp, mod - 2);
        for (int i = 0; i < lim; i += (m << 1)) {
            int o = 1;
            for (int j = 0; j < m; ++j, o = o * tmp % mod) {
                int t = o * a[i + j + m] % mod, u = a[i + j];
                a[i + j] = (u + t) % mod, a[i + j + m] = (u - t + mod) % mod;
            }
        }
     }
     if (opt == -1) for (int i = 0, in = qpow (lim, mod - 2); i < lim; ++i) a[i] = a[i] * in % mod;
}
signed main() {
    read (n); pw[0] = 1;
    for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * i % mod;
    in[n] = qpow (pw[n], mod - 2);
    for (int i = n; i >= 1; --i) in[i - 1] = in[i] * i % mod;
    for (int i = 1, u, v; i < n; ++i)
        read (u), read (v), add (u, v), add (v, u);
    dfs (1, 0);
    while (lim <= n + n) lim <<= 1, ++len;
    for (int i = 0; i < lim; ++i)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
    for (int i = 0; i <= n; ++i) a[i] = cc[i] * pw[i] % mod;
    for (int i = 0; i <= n; ++i) b[i] = in[i] % mod;
    reverse (a, a + n + 1);
    ntt (a, 1), ntt (b, 1);
    for (int i = 0; i < lim; ++i) a[i] = a[i] * b[i] % mod;
    ntt (a, -1);
    for (int i = 1; i <= n; ++i) {
        int res = n * C (n, i) - in[i] * a[n - i];
        printf ("%lld
", (res % mod + mod) % mod);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/whx666/p/agc005.html