• 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;
    }
    
  • 相关阅读:
    java 单向链表实现
    super、this
    Java程序员们最常犯的10个错误
    Codeforces-1323D Present
    Codeforces-1323E Instant Noodles
    Codeforces-1312E Array Shrinking
    Codeforces-1327D Infinite Path
    Codeforces-1326D Prefix-Suffix Palindrome
    HDU-5885 XM Reserves
    NTT(快速数论变换)用到的各种素数及原根
  • 原文地址:https://www.cnblogs.com/whx666/p/14269435.html
Copyright © 2020-2023  润新知