题解 | 牛客周赛 Round 156

A 小红找数字

知识点:字符串、模拟

对 $s$ 逐字符扫描。第一个数字字符就是答案;若不存在,输出 $-1$​。

isdigitstd 命名空间直接提供的可以判断字符是否是数字的函数。

时间复杂度 $\mathcal{O}(|s|)$。

void solve() {
    string s;
    cin >> s;
    for (auto v : s)
        if (isdigit(v)) return cout << v << endl, void();
    cout << -1 << endl;
}

B 小红的回文串

知识点:字符串、枚举、回文判断

枚举待删除的字符 $c\in[\texttt{a},\texttt{z}]$,构造删除所有 $c$ 后的字符串 $t$。判断 $t$ 是否和原串相同,再判断是否等于其反串即可。

时间复杂度 $\mathcal{O}(26n)$。

void solve() {
    int n, ans = 0;
    string s;
    cin >> n >> s;
    for (char c = 'a'; c <= 'z'; ++c) {
        string t;
        for (auto v : s)
            if (v != c) t += v;
        ans += s != t && t == string(t.rbegin(), t.rend());
    }
    cout << ans << endl;
}

C 小红的权值

知识点:贪心、排序、前缀和、二分

将 $a_i$ 改成 $x$ 后,恰好能消去其全部贡献。设

$$b_i=|a_i-x|,\qquad S=\sum_{i=1}^{n}b_i.$$

要使权值不超过 $k$,需要消去至少 $S-k$ 的贡献。每次操作应优先消去最大的 $b_i$,将所有贡献降序排序并求前缀和,找到最小的满足 $P_j\ge S-k$ 的 $j$ 即可。

时间复杂度 $\mathcal{O}(n\log n+q\log n)$。

void solve() {
    int n, q, x, s = 0;
    cin >> n >> q >> x;
    vector<int> a(n);
    for (int i = 0; i < n; ++i)
        cin >> a[i], a[i] = abs(a[i] - x), s += a[i];

    sort(a.rbegin(), a.rend());
    vector<int> pre(1);
    for (auto v : a) pre.push_back(pre.back() + v);

    while (q--) {
        int k;
        cin >> k;
        cout << lower_bound(pre.begin(), pre.end(), s - k) - pre.begin() << endl;
    }
}

D 小红的01矩阵

知识点:状态压缩、计数 DP、二进制枚举

每列只有 $3$ 个位置,因此完整列型仅有 $2^3=8$ 种。若 $n>8$,由抽屉原理可知必然存在两列相同,答案为 $0$。

设 $dp[\mathrm{mask}]$ 表示已处理的列中,已使用的列型集合为 $\mathrm{mask}$ 时的方案数。对当前列枚举所有与输入限制兼容的列型 $x$,只要 $x$ 尚未出现,就转移到 $\mathrm{mask}\cup{x}$。最终累加所有状态。

时间复杂度 $\mathcal{O}(n\cdot2^8\cdot8)$。

void solve() {
    int n;
    cin >> n;
    if (n > 8) return cout << 0 << endl, void();

    vector<string> s(3);
    for (int i = 0; i < 3; ++i) cin >> s[i];

    array<int, 1 << 8> dp{};
    dp[0] = 1;

    for (int i = 0; i < n; ++i) {
        int pos = 0;
        for (int x = 0; x < 8; ++x) {
            bool f = 1;
            for (int j = 0; j < 3; ++j) {
                if (s[j][i] != '?' && (x >> j & 1) != s[j][i] - '0') {
                    f = 0;
                    break;
                }
            }
            if (f) pos |= 1 << x;
        }

        array<int, 1 << 8> ndp{};
        for (int mask = 0; mask < (1 << 8); ++mask) {
            for (int x = 0; x < 8; ++x) {
                if ((pos >> x & 1) && !(mask >> x & 1)) {
                    ndp[mask | 1 << x] += dp[mask];
                }
            }
        }
        dp.swap(ndp);
    }

    cout << accumulate(dp.begin(), dp.end(), 0LL) << endl;
}

E 小红的树染色

知识点:树的直径、LCA、倍增

先求初始红点集合 $R$ 的直径端点 $a,b$,直径为 $d$。树上有性质:

$$\max_{v\in R}\operatorname{dist}(u,v)=\max\bigl(\operatorname{dist}(u,a),\operatorname{dist}(u,b)\bigr).$$

将 $i$ 染红后,原红点之间的最大距离仍为 $d$;新增的最远点对一定包含 $i$。因此答案为:

$$\max\bigl(d,\operatorname{dist}(i,a),\operatorname{dist}(i,b)\bigr).$$

代码扫描红点并维护当前直径端点,再用倍增求 $\texttt{LCA}$ 与距离。

时间复杂度 $\mathcal{O}(n\log n)$。

void solve() {
    int n;
    string s;
    cin >> n >> s;

    vector<int> r;
    for (int i = 1; i <= n; ++i)
        if (s[i - 1] == '1') r.push_back(i);

    Tree tr(n);
    for (int i = 1, u, v; i < n; ++i) {
        cin >> u >> v;
        tr.add(u, v);
    }
    tr.work();

    int a = r[0], b = r[0], d = 0;
    for (auto v : r) {
        int da = tr.askDis(v, a), db = tr.askDis(v, b);
        if (max(da, db) <= d) continue;
        if (da >= db)
            b = v, d = da;
        else
            a = v, d = db;
    }

    for (int i = 1; i <= n; ++i) {
        cout << max({d, tr.askDis(i, a), tr.askDis(i, b)}) << endl;
    }
}

F 小红的排列计数

知识点:排列计数、组合 DP、插入法

法 $1$​ :

推导 $1$ :

设 $f_{m,j}$ 表示长度为 $m$ 的排列中,恰有 $j$ 个局部极小值的方案数。向长度为 $m$ 的排列插入新的最小值,并将原值全部加 $1$。

已有 $j$ 个极小值时,两个端点间隙和每个极小值相邻的两个间隙共有 $2j+2$ 个。在这些位置插入时,新极小值会抵消相邻旧极小值,或位于端点,因此极小值数量不变。其余 $m-2j-1$ 个间隙会新增一个极小值。

$$f_{m+1,j}=(2j+2)f_{m,j}+(m-2j+1)f_{m,j-1}.$$

初值为 $f_{2,0}=2$,长度为 $1$ 时答案单独为 $1$​​。

时间复杂度 $\mathcal{O}(nk)$。

void solve() {
    int n, k;
    cin >> n >> k;
    if (k < 0 || k > (n - 1) / 2) return cout << 0 << endl, void();
    if (n == 1) return cout << 1 << endl, void();

    vector<Z> dp(k + 1), ndp(k + 1);
    dp[0] = 2;

    for (int len = 2; len < n; ++len) {
        fill(ndp.begin(), ndp.end(), 0);
        for (int j = 0; j <= min(k, len / 2); ++j) {
            ndp[j] += (2 * j + 2) * dp[j];
            if (j) ndp[j] += (len - 2 * j + 1) * dp[j - 1];
        }
        dp.swap(ndp);
    }

    cout << dp[k] << endl;
}

推导 $2$ :

记 $a_{n,k}$ 为长度为 $n$ 的排列中,恰有 $k$ 个内部极小值的方案数。对排列取补 $q_i=n+1-p_i$,内部极小值会一一对应为内部极大值,因此可直接统计内部峰值。

$$P_n(y)=\sum_{k\ge0}a_{n,k}y^k, \qquad F(x,y)=\sum_{n\ge0}P_n(y)\frac{x^n}{n!}.$$

内部峰值的经典指数型生成函数为:

$$F(x,y)= \frac{\sqrt{1-y}\cosh\left(x\sqrt{1-y}\right)} {\sqrt{1-y}\cosh\left(x\sqrt{1-y}\right) -\sinh\left(x\sqrt{1-y}\right)} = \frac{1} {1-\dfrac{\tanh\left(x\sqrt{1-y}\right)}{\sqrt{1-y}}}.$$

因此所求答案就是:

$$\boxed{ a_{n,k}=n!\,[x^ny^k]F(x,y) }$$

对上式求偏导并整理,可得:

$$(1-xy)F_x = (2-y)F+y-1+2y(1-y)F_y.$$

比较 $x^ny^k$ 的系数,得到:

$$a_{n+1,k} = (2k+2)a_{n,k} + (n-2k+1)a_{n,k-1}.$$

代码同推导 $1$


法 $2$ :

从刚才的推导 $2$ 继续:

设 $a_{n,k}$ 为答案,$A_n(q)$ 为欧拉多项式:

$$A_n(q)=\sum_{j=0}^{n-1}\left\langle {n\atop j}\right\rangle q^j.$$

由补排列将局部极小值转化为局部极大值,可得计数多项式:

$$\sum_k a_{n,k}y^k = (1+\sqrt{1-y})^{n-1} A_n\left(\frac{1-\sqrt{1-y}}{1+\sqrt{1-y}}\right).$$

因此:

$$\boxed{ a_{n,k}=[y^k]\, (1+\sqrt{1-y})^{n-1} A_n\left(\frac{1-\sqrt{1-y}}{1+\sqrt{1-y}}\right) }$$

欧拉数可直接写为:

$$\left\langle {n\atop j}\right\rangle = \sum_{r=0}^{j+1} (-1)^r\binom{n+1}{r}(j+1-r)^n.$$

为避免处理根号,对 $k\ge1$ 使用拉格朗日反演。令 $m=n-2k$,则:

$$a_{n,k}= \frac{2^{n-2k-1}}{k} [q^{k-1}](1+q)^{2k-n} \left((1+q)A_n'(q)-(n-1)A_n(q)\right).$$

设 $E_j=\left\langle n\atop j\right\rangle$,并令:

$$f_r=(-1)^r\binom{n+1}{r}, \qquad g_t=t^n, \qquad E_j=(f*g)_{j+1}.$$

再令:

$$c_t=(-1)^t\binom{m+t-1}{t}.$$

于是可直接得到:

$$a_{n,k}= \frac{2^{n-2k-1}}{k} \sum_{j=0}^{k-1} \left((j+1)E_{j+1}+(j-n+1)E_j\right)c_{k-1-j}\pmod{998244353}.$$

当 $2k>n-1$ 时答案为 $0$。对剩余合法的 $k$,先用一次 $\texttt{NTT}$ 卷积求出所需欧拉数,再计算上式即可,这样可以处理 $n,k$ 很大的情况。

时间复杂度 $\mathcal{O}(k\log k+k\log n)$。

void solve() {
    int n, k;
    cin >> n >> k;
    if (k > (n - 1) / 2) return cout << 0 << endl, void();
    vector<Z> f(k + 2), g(k + 2);
    Z cb = 1;
    f[0] = 1;
    for (int r = 1; r <= k + 1; ++r) cb *= Z(n + 2 - r) / r, f[r] = (r & 1 ? -cb : cb);
    for (int t = 1; t <= k + 1; ++t) g[t] = Z(t).pow(n);
    auto h = NTT::mul(f, g);
    vector<Z> e(k + 1);
    for (int j = 0; j <= k; ++j) e[j] = h[j + 1];
    int m = n - 2 * k;
    vector<Z> c(k);
    c[0] = 1;
    for (int t = 1; t < k; ++t) c[t] = -c[t - 1] * Z(m + t - 1) / t;
    Z s = 0;
    for (int j = 0; j < k; ++j) {
        Z b = Z(j + 1) * e[j + 1] + Z(j - n + 1) * e[j];
        s += b * c[k - 1 - j];
    }
    cout << Z(2).pow(n - 2 * k - 1) * s / k << endl;
}

约定

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define pii array<int, 2>
#define endl "\n"

void solve() {

}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cout << fixed << setprecision(20);
    int t = 1;
    cin >> t;
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

使用到的算法模板见 $\texttt{github}$ 仓库 林月的XCPC模板库

评论

  1. unlocker的头像
    53 分前
    2026-8-10 11:32:36

    unlocker.ai – The Ultimate AI Tool for Bypassing Restrictions and Unlocking Content Seamlessly!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇