2018冬令营模拟测试赛(二)

2018冬令营模拟测试赛(二)

[Problem A]最小生成树

试题描述

TAT

QAQ

TAQ

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

这是一道坑了我的结论题(考场上推了半天结果得零分,最气的是发现再特判一下就 A 了!!!

我们不妨让最终的最小生成树长成一条链,然后这条链的边权从左到右依次单调不降。

那么为了构建出 (m) 条边的无向图,我们可以贪心地再左边加边,即 (t)(1) 开始计,每次将前 (t) 个点加边加成完全图,然后 (t)(1),直到 (t=n)(注意最开始要先把所有节点按顺序串成一条链)。

那么一个安排边权的贪心策略来了:将前 (n-1) 个点之间连接的所有边的边权安排成 (1),然后剩下的边(即和第 (n) 个点连的所有边)边权安排成 (n-2)

这样显然不是最优的,但这给我们提供了一个很好的思路,接下来的算法就是在上面这个建图基础上的。

我们假设一开始所有边权都是 (0),那么有两种选择:一种是将所有边权 (+1),一种是将和最后一个点有关的所有边权 (+1);目标是让生成树的所有边之和 (=s),那么这两种选择就相当于两种物品,背包的容量为 (s),第一种体积为 (1),代价为 (x)(即和最后一个点有关的边数)(注意这里用代价是因为我们要最小化这个值,而“价值”是一个褒义词故不用它),第二种物品物品体积为 (n-1)代价(m)。那么假设我们选择 (a) 个第一种物品,(b) 个第二种物品,令 (N = n-1),则有 (a + bN = s),要最小化 (ax + bm) 的值;把 (a)(b) 表示得到最小化 ((s-bN)x + bm = sx - Nxb + mb = (m-Nx)b + sx),当 (m-Nx ge 0)(b = 0) 即可,否则让 (b) 取到最大,也即 (lfloor frac{s}{N} floor)。于是答案可以 (O(1)) 得到。

当然不要忘掉一个我考场上漏掉的特殊情况。容易发现我们上面列出的两个并不是全部可选的物品,因为我们还可以选择后任意多个点之间的所有边 (+1)——只需要保证每时每刻那条链的边权单调不降就行。这个问题的解决方案是所有边取平均值 (frac{s}{m})(前面的边下取整,后面的边上取整)就好了。至于为啥,我不知道。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
#define LL long long

LL read() {
    LL x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

LL calc(int x) { return (LL)x * (x - 1) >> 1; }

LL solve(int n, LL m, LL s, LL restm) {
    if(restm <= calc(n - 1)) return min(restm, calc(n - 1)) + s + 1;
    n--;
    restm -= calc(n); restm++;
    // printf("(1, %lld)
(%d, %lld)
s = %lld
", restm, n, m, s);
    if(m - restm * n >= 0) return s * restm + m;
    LL a = s, b = 0;
    LL k = a / n; b += k;
    return s * restm + b * (m - restm * n) + m;
}
LL avg(int n, LL m, LL s) {
    LL lo = s / (n - 1), hi = (s + n - 2) / (n - 1);
    if(lo == hi) return m * lo;
    n--;
    LL restm = m - 1 - calc(n) + 1, sma = lo * n + n - s, pre = min(calc(sma + 1) - sma + 1, m - n + 1) + sma - 1, suf = m - pre;
    return pre * lo + suf * hi;
}

int main() {
    int T = read();

    while(T--) {
        int n = read();
        LL m = read(), s = read() - n + 1, restm = m - 1;
        printf("%lld
", min(solve(n, m, s, restm), avg(n, m, s + n - 1)));
    }

    return 0;
}

[Problem B]没有上司的舞会

试题描述

Q_Q

ToT

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

用数据结构维护 dp 信息。一句话解决问题。

好吧还是讲讲,我们考虑用 LCT 维护动态加点的操作,每个节点 (i) 记录 (f_0(i))(f_1(i)) 分别表示不选和选择它自己最多能从它以及它所有 LCT 上“非访问边”中选择多少个节点。(access) 操作的时候直接对 (f_0)(f_1) 进行加减操作实现加入一个儿子和删掉一个儿子。此外在 splay 中搞一个 (F_{a,b}(i))(a, b) 都是 (0)(1) 的整数)表示对于 splay 中的子树 (i),首、尾选不选的 dp 值,这样就可以方便地合并了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 300010
#define oo 2147483647

struct LCT {
    int f[maxn][2], F[maxn][2][2];
    int fa[maxn], ch[maxn][2];

    bool isrt(int u) { return ch[fa[u]][0] != u && ch[fa[u]][1] != u; }

    void merge(int to, int l, int r) {
        int nf[2][2];
        nf[0][0] = max(max(F[l][0][1] >= 0 && F[r][0][0] >= 0 ? F[l][0][1] + F[r][0][0] : -1, F[l][0][0] >= 0 && F[r][1][0] >= 0 ? F[l][0][0] + F[r][1][0] : -1), F[l][0][0] >= 0 && F[r][0][0] >= 0 ? F[l][0][0] + F[r][0][0] : -1);
        nf[0][1] = max(max(F[l][0][1] >= 0 && F[r][0][1] >= 0 ? F[l][0][1] + F[r][0][1] : -1, F[l][0][0] >= 0 && F[r][1][1] >= 0 ? F[l][0][0] + F[r][1][1] : -1), F[l][0][0] >= 0 && F[r][0][1] >= 0 ? F[l][0][0] + F[r][0][1] : -1);
        nf[1][0] = max(max(F[l][1][1] >= 0 && F[r][0][0] >= 0 ? F[l][1][1] + F[r][0][0] : -1, F[l][1][0] >= 0 && F[r][1][0] >= 0 ? F[l][1][0] + F[r][1][0] : -1), F[l][1][0] >= 0 && F[r][0][0] >= 0 ? F[l][1][0] + F[r][0][0] : -1);
        nf[1][1] = max(max(F[l][1][1] >= 0 && F[r][0][1] >= 0 ? F[l][1][1] + F[r][0][1] : -1, F[l][1][0] >= 0 && F[r][1][1] >= 0 ? F[l][1][0] + F[r][1][1] : -1), F[l][1][0] >= 0 && F[r][0][1] >= 0 ? F[l][1][0] + F[r][0][1] : -1);
        memcpy(F[to], nf, sizeof(nf));
        return ;
    }
    void maintain(int u) {
        F[u][0][0] = f[u][0]; F[u][1][1] = f[u][1];
        F[u][0][1] = F[u][1][0] = -1;
        if(ch[u][0]) merge(u, ch[u][0], u);
        if(ch[u][1]) merge(u, u, ch[u][1]);
        return ;
    }
    void rotate(int u) {
        int y = fa[u], z = fa[y], l = 0, r = 1;
        if(!isrt(y)) ch[z][ch[z][1]==y] = u;
        if(ch[y][1] == u) swap(l, r);
        fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
        ch[y][l] = ch[u][r]; ch[u][r] = y;
        return maintain(y);
    }
    void splay(int u) {
        while(!isrt(u)) {
            int y = fa[u], z = fa[y];
            if(!isrt(y)) {
                if(ch[y][0] == u ^ ch[z][0] == y) rotate(u);
                else rotate(y);
            }
            rotate(u);
        }
        return maintain(u);
    }

    void access(int u) {
        splay(u);
        int fson[2];
        if(ch[u][1]) {
            fson[0] = max(F[ch[u][1]][0][0], F[ch[u][1]][0][1]);
            fson[1] = max(F[ch[u][1]][1][0], F[ch[u][1]][1][1]);
            f[u][0] += max(fson[0], fson[1]); f[u][1] += fson[0];
        }
        ch[u][1] = 0;
        maintain(u);
        while(fa[u]) {
            splay(fa[u]);
            if(ch[fa[u]][1]) {
                fson[0] = max(F[ch[fa[u]][1]][0][0], F[ch[fa[u]][1]][0][1]);
                fson[1] = max(F[ch[fa[u]][1]][1][0], F[ch[fa[u]][1]][1][1]);
                f[fa[u]][0] += max(fson[0], fson[1]); f[fa[u]][1] += fson[0];
            }
            fson[0] = max(F[u][0][0], F[u][0][1]);
            fson[1] = max(F[u][1][0], F[u][1][1]);
            f[fa[u]][0] -= max(fson[0], fson[1]); f[fa[u]][1] -= fson[0];
            ch[fa[u]][1] = u;
            maintain(fa[u]);
            splay(u);
        }
        return ;
    }
    void link(int a, int b) { // b should be a leaf
        f[b][0] = 0; f[b][1] = 1; maintain(b);
        access(a); fa[b] = a; ch[a][1] = b;
        maintain(a);
        return ;
    }
} sol;

int main() {
    int n = read() + 1, type = read(), lstans = 0;
    // printf("%d %d
", n, type);
    sol.f[1][0] = 0; sol.f[1][1] = 1; sol.maintain(1);
    rep(i, 2, n) {
        int fa = (read() ^ type * lstans) + 1;
        sol.link(fa, i);
        sol.access(1);
        printf("%d
", lstans = max(sol.f[1][0], sol.f[1][1]));
    }

    return 0;
}

[Problem C]排列问题

试题描述

QwQ

T_T

TwT

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

我去这题有点毒瘤。

首先你需要想到容斥 dp,其它 dp 方式都无法搞定正解。

(f(i, j)) 表示对于前 (i) 种颜色,序列强行分了 (j) 段的方案数,那么转移就简单了,枚举最后一种颜色分多少段即可,然后推式子搞搞成为卷积的形式:(f(i, j) = sum_{k=1}^{mathrm{min}{ a_i, j }} { f(i-1, j-k) cdot C_j^k cdot C_{a_i-1}^{k-1} } = sum_{k=1}^{mathrm{min}{ a_i, j }} { f(i-1, j - k) cdot frac{j!}{k!(j-k)!} cdot frac{(a_i-1)!}{(k-1)!(a_i-k)!} } Rightarrow frac{f(i, j)}{j!} = sum_{k=1}^{mathrm{min}{ a_i, j }} { frac{f(i-1, j-k)}{(j-k)!} cdot frac{(a_i-1)!}{k!(k-1)!(a_i-k)!} })

于是令 (A_i(x) = sum_{k=1}^{a_i} { frac{(a_i - 1)!}{k!(k-1)!(a_i-k)!} cdot x^k })(F_0(x) = 1),那么得到的 (F_n(x) = F_0(x) cdot prod_{i=1}^n {A_i(x)}) 所有系数就是最后的 (f(n, i) cdot i!),由于乘法有结合律,可以先分治 FFT 求出后面那一坨 (A_i(x)) 乘起来的多项式,然后再用 (F_0(x)) 去乘就好了。

然后我们还要容斥,显然上面会重复计算,上面的 dp 会对球序列多分几段,所以要依次减掉多分一段的,加上多分两段的,减去多分三段的……形式化地:令 (forall i in [1, m], g(i) = f(n, i)),最终分 (i) 段的方案数为 (Ans_i),则 (Ans_i = sum_{j=1}^i { (-1)^{i-j} cdot g(j) cdot C_{m-j}^{i-j} } Rightarrow Ans_i cdot (m-i)! = sum_{j=1}^i {g(j) cdot (m-j)! cdot frac{(-1)^{i-j}}{(i-j)!} }),还是卷积!再 FFT 一下就好了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 524288
#define MOD 998244353
#define Groot 3
#define LL long long

int rev[maxn];
int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}
void FFT(vector <int> &A, int len, int tp) {
    int n = 1 << len;
    rep(i, 0, n - 1) if(i < rev[i]) swap(A[i], A[rev[i]]);
    rep(i, 1, len) {
        int wn = Pow(Groot, MOD - 1 >> i);
        if(tp < 0) wn = Pow(wn, MOD - 2);
        for(int j = 0; j < n; j += 1 << i) {
            int w = 1;
            rep(k, 0, (1 << i >> 1) - 1) {
                int al = A[j+k], ar = (LL)w * A[j+k+(1<<i>>1)] % MOD;
                A[j+k] = (al + ar) % MOD;
                A[j+k+(1<<i>>1)] = (al - ar + MOD) % MOD;
                w = (LL)w * wn % MOD;
            }
        }
    }
    if(tp < 0) rep(i, 0, n - 1) A[i] = (LL)A[i] * Pow(n, MOD - 2) % MOD;
    return ;
}
void Mul(vector <int> &A, vector <int> &B) {
    int n = A.size() - 1, N = 1, len = 0;
    while(N <= n) N <<= 1, len++;
    rep(i, 1, N - 1) rev[i] = (rev[i>>1] >> 1) | ((i & 1) << len - 1);
    FFT(A, len, 1); FFT(B, len, 1);
    rep(i, 0, N - 1) A[i] = (LL)A[i] * B[i] % MOD;
    FFT(A, len, -1);
    return ;
}

int m, a[maxn], fac[maxn], ifac[maxn];

vector <int> GetTrans(int l, int r) {
    if(l == r) {
        vector <int> A(a[l] + 1);
        A[0] = 0;
        rep(i, 1, a[r]) A[i] = (LL)fac[a[l]-1] * ifac[i] % MOD * ifac[i-1] % MOD * ifac[a[r]-i] % MOD;
        return A;
    }
    int mid = l + r >> 1;
    vector <int> A = GetTrans(l, mid), B = GetTrans(mid + 1, r);
    int asiz = A.size(), bsiz = B.size(), siz = asiz + bsiz, need = 1;
    while(need <= siz - 2) need <<= 1;
    A.resize(need); B.resize(need);
    rep(i, asiz, need - 1) A[i] = 0; rep(i, bsiz, need - 1) B[i] = 0;
    Mul(A, B);
    vector <int> E(siz-1);
    rep(i, 0, siz - 2) E[i] = A[i];
    return E;
}

int main() {
    int n = read();
    rep(i, 1, n) a[i] = read(), m += a[i];

    fac[0] = ifac[0] = 1;
    rep(i, 1, m) fac[i] = (LL)fac[i-1] * i % MOD, ifac[i] = (LL)ifac[i-1] * Pow(i, MOD - 2) % MOD;

    vector <int> A = GetTrans(1, n), F;
    int siz = A.size(), need = 1;
    while(need <= siz - 1) need <<= 1;
    F.resize(need); F[0] = 1; rep(i, 1, need - 1) F[i] = 0;
    A.resize(need); rep(i, siz, need - 1) A[i] = 0;
    Mul(F, A);
    rep(i, 0, siz - 1) F[i] = (LL)F[i] * fac[i] % MOD;
    // printf("F[0~%d]: ", m); rep(i, 0, m) printf("%d%c", F[i], i < m ? ' ' : '
');

    need = 1;
    while(need <= (m << 1)) need <<= 1;
    vector <int> G(need), T(need);
    G[0] = 0; rep(i, 1, m) G[i] = (LL)F[i] * fac[m-i] % MOD; rep(i, m + 1, need - 1) G[i] = 0;
    // rep(i, 0, m) printf("%d%c", G[i], i < m ? ' ' : '
');
    rep(i, 0, m) T[i] = (i & 1) ? MOD - ifac[i] : ifac[i]; rep(i, m + 1, need - 1) T[i] = 0;
    Mul(G, T);
    rep(i, 0, m) G[i] = (LL)G[i] * ifac[m-i] % MOD;

    int q = read();
    while(q--) {
        int k = read();
        printf("%d
", G[m-k]);
    }

    return 0;
}
原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8085260.html