题解 | 牛客周赛 Round 158

A 小月的材料

知识点:模拟、绝对值

直接按定义计算 $3$ 段差值并加权即可:

$$\mathrm{ans}=4|a-b|+2|b-c|+|c-a|。$$

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

void solve() {
    int a, b, c;
    cin >> a >> b >> c;
    cout << 4 * abs(a - b) + 2 * abs(b - c) + abs(c - a) << endl;
}

B 小月的点歌台

知识点:字符串、计数、模拟

从左到右扫描字符串。处理第 $i$ 次点歌时,将对应类别的计数加一;第 $1$ 次使计数达到 $3$ 的位置就是答案。若扫描结束仍没有类别达到 $3$ 次,则输出 $-1$。

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

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

    unordered_map<char, int> mp;
    for (int i = 0; i < n; ++i) {
        ++mp[s[i]];
        if (mp[s[i]] == 3) return cout << i + 1 << endl, void();
    }
    cout << -1 << endl;
}

C 小月的书架

知识点:哈希表、计数、逆向扫描

固定位置 $i$,只需比较 $a_i$ 在它左侧和右侧的出现次数。预先统计每个编号的总出现次数,然后从右向左枚举:

  • 先把当前位置从总计数中删去,此时该编号在计数表中的数量就是左侧数量;
  • 另一个计数表记录已经扫过的部分,也就是右侧数量;
  • 两者相等时,把位置 $i$ 加入答案。

时间复杂度平均为 $\mathcal{O}(n)$。

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    unordered_map<int, int> mp;
    for (int i = 1; i <= n; ++i) cin >> a[i], mp[a[i]]++;
    
    unordered_map<int, int> mp2;
    int ans = 0;
    for (int i = n; i >= 1; --i) {
        mp[a[i]]--;
        if (mp[a[i]] == mp2[a[i]]) ans += i;
        mp2[a[i]]++;
    }
    cout << ans << endl;
}

D 小月的相同数对

知识点:双指针、滑动窗口、计数

设当前窗口为 $[l,r]$,并记其中相同数对的数量为

$$s=\sum_x\binom{\operatorname{cnt}_x}{2}。$$

右端点加入一个值 $x$ 时,新产生的数对恰好是窗口中原有的 $x$ 的数量,因此令 $s$ 增加 $\operatorname{cnt}_x$。当 $s\ge k$ 时不断移动左端点,并在每次移动前用当前长度更新答案。若被移出的值出现了 $t$ 次,它参与的数对正好有 $t-1$ 个,所以令 $s$ 减去 $t-1$。

左右端点都只向右移动,故每个位置至多进入、离开窗口一次。

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

constexpr int inf = 1e9 + 7;

void solve() {
    int n, k;
    cin >> n >> k;

    vector<int> a(n);
    for (auto &v : a) cin >> v;

    int s = 0, l = 0, ans = inf;
    vector<int> cnt(n + 1);

    for (int r = 0; r < n; ++r) {
        s += cnt[a[r]];
        ++cnt[a[r]];
        while (l <= r && s >= k) {
            ans = min(ans, r - l + 1);
            s -= cnt[a[l]] - 1;
            --cnt[a[l]];
            ++l;
        }
    }
    cout << (ans == inf ? -1 : ans) << endl;
}

E 小月的相邻数组

知识点:差分变形、树状数组、单点修改区间查询

将条件移项:

$$a_i-a_{i-1}<a_{i+1}-a_i \iff a_{i-1}+a_{i+1}>2a_i。$$

定义 $b_i$ 表示位置 $i$ 是否满足该条件,仅需维护 $2\le i\le n-1$ 的位置。修改 $a_p$ 只会影响 $b_{p-1}$、$b_p$、$b_{p+1}$,逐个重算即可。询问 $l<i<r$ 等价于查询 $b$ 在区间 $[l+1,r-1]$ 的和,用树状数组维护这些点值。

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

template <class T> struct BIT {...} // 树状数组

void solve() {
    int n, q;
    cin >> n >> q;

    vector<int> a(n + 1), b(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i];

    auto calc = [&](int i) {
        return (a[i - 1] + a[i + 1] > 2 * a[i]) ? 1LL : 0LL;
    };

    for (int i = 2; i < n; ++i) b[i] = calc(i);

    BIT<int> tr(n);
    tr.build(b);

    while (q--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1) {
            a[x] = y;
            for (int i = max(2LL, x - 1); i <= min(n - 1, x + 1); ++i) {
                int v = calc(i);
                tr.modify(i, v - b[i]);
                b[i] = v;
            }
        } else {
            cout << tr.ask(x + 1, y - 1) << endl;
        }
    }
}

F 小月的数组

知识点:模计数、线性递推、单位根反演、生成函数

法 $1$:矩阵快速幂

令 $F_n[j]$ 表示处理完 $n$ 个位置后,当前和模 $4$ 为 $j$ 的方案数,得到递推方程组。
$$\begin{aligned}F_{n+1}[0]&=F_n[0]+F_n[1]+F_n[3],\\F_{n+1}[1]&=F_n[0]+F_n[1]+F_n[2],\\F_{n+1}[2]&=F_n[1]+F_n[2]+F_n[3],\\F_{n+1}[3]&=F_n[0]+F_n[2]+F_n[3].\end{aligned}$$
可以直接写出矩阵
$$\begin{matrix}1 & 1 & 0 & 1\\1 & 1 & 1 & 0\\0 & 1 & 1 & 1\\1 & 0 & 1 & 1\\\end{matrix}$$
则初始向量为 $v_0=(1,0,0,0)$,并有 $v_n=v_0M^n$。答案就是 $M^n$ 的第 $0$ 行第 $r$ 列。

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

constexpr int mod = 998244353;

using Z = MInt<mod>;

template <class T> struct Matrix {...} // 矩阵

void solve() {
    int n, r;
    cin >> n >> r;

    Matrix<Z> f(4, 4);
    for (int i = 0; i < 4; ++i) {
        f[i][i] += 1;
        f[i][(i + 1) % 4] += 1;
        f[i][(i + 3) % 4] += 1;
    }
    cout << f.ksm(n)[0][r] << endl;
}

法 $2$:推式子

还是从刚刚余数 $dp$ 的方程组出发,不妨记 $A_n,B_n,C_n,D_n$ 分别为和模 $4$ 等于 $0,1,2,3$ 的方案数。

初值为 $(A_0,B_0,C_0,D_0)=(1,0,0,0)$。由递推可知 $B_n=D_n$;同时
$$A_{n+1}-C_{n+1}=A_n-C_n,$$

所以始终有 $A_n-C_n=1$。令 $S_n=A_n+C_n$,并把 $B_n$ 记作两个奇数余数各自的数量,则

$$S_{n+1}=S_n+4B_n,\qquad B_{n+1}=S_n+B_n。$$

再令 $X_n=S_n+2B_n$、$Y_n=S_n-2B_n$,便有

$$X_{n+1}=3X_n,\qquad Y_{n+1}=-Y_n,$$

且 $X_0=Y_0=1$。因此

$$S_n=\frac{3^n+(-1)^n}{2},\qquad B_n=D_n=\frac{3^n-(-1)^n}{4}。$$

结合 $A_n-C_n=1$,最终得到

$$\begin{aligned}f_{n,0}&=\frac{3^n+(-1)^n+2}{4},\\f_{n,1}&=\frac{3^n-(-1)^n}{4},\\f_{n,2}&=\frac{3^n+(-1)^n-2}{4},\\f_{n,3}&=\frac{3^n-(-1)^n}{4}.\end{aligned}$$

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

constexpr int mod = 998244353;

using Z = MInt<mod>;

void solve() {
    int n, r;
    cin >> n >> r;

    Z p = Z(3).pow(n);
    Z e = (n & 1) ? Z(-1) : Z(1);
    array<Z, 4> ans = {
        (p + e + Z(2)) / Z(4),
        (p - e) / Z(4),
        (p + e - Z(2)) / Z(4),
        (p - e) / Z(4)
    };
    cout << ans[r] << endl;
}

法 $3$:单位根反演

令 $\omega=\mathrm{i}$ 为 $4$ 次单位根。单位根筛选公式把总和模 $4$ 为 $r$ 的项筛出:

$$f_{n,r}=\frac14\sum_{j=0}^{3}\omega^{-rj}(1+\omega^j+\omega^{3j})^n。$$

$4$ 个代入点的基函数值非常简单:

$$1+1+1=3,\qquad 1-1-1=-1,\qquad 1+\omega+\omega^3=1,$$

其中最后一个等式对 $\omega$ 和 $\omega^3$ 都成立。因此 $j=0$ 贡献 $3^n$,$j=2$ 贡献 $(-1)^{n+r}$,而 $j=1,3$ 的合计贡献在 $r=0,1,2,3$ 时分别为 $2,0,-2,0$。于是

$$f_{n,r}=\frac{3^n+(-1)^{n+r}+2\cos(\pi r/2)}{4},$$

实际计算时只需按 $r$ 的奇偶性及是否为 $0$、$2$ 分类,不需要进行复数运算。

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

constexpr int mod = 998244353;

using Z = MInt<mod>;

void solve() {
    int n, r;
    cin >> n >> r;

    Z e = (n & 1) ? Z(-1) : Z(1);
    Z ans = Z(3).pow(n);
    if (r & 1) ans -= e;
    else ans += e;
    if (r == 0) ans += 2;
    if (r == 2) ans -= 2;
    cout << ans / Z(4) << endl;
}

法 $4$:生成函数

把每个元素的取值看成 $1$ 次幂次贡献,单个位置的生成函数为 $1+x+x^3$,因此长度为 $n$ 的所有数组对应

$$F_n(x)=(1+x+x^3)^n。$$

我们只关心指数模 $4$ 的余数,所以在商环 $\mathbb{Z}[x]/(x^4-1)$ 中计算即可:

$$F_n(x)\equiv\sum_{r=0}^{3}f_{n,r}x^r\pmod{x^4-1}。$$

于是多项式乘法就是循环卷积:$x^i\cdot x^j$ 的指数折回 $(i+j)\bmod 4$。对基多项式 $1+x+x^3$ 做二进制快速幂,最终向量的第 $r$ 项即为答案。

每次循环卷积只需处理 $4^2$ 对系数。

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

constexpr int mod = 998244353;

using Z = MInt<mod>;

void solve() {
    int n, r;
    cin >> n >> r;

    using V = array<Z, 4>;
    auto mul = [](const V &a, const V &b) {
        V c{};
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                c[(i + j) % 4] += a[i] * b[j];
            }
        }
        return c;
    };

    V ans{1, 0, 0, 0}, base{1, 1, 0, 1};
    while (n) {
        if (n & 1) ans = mul(ans, base);
        base = mul(base, base);
        n >>= 1;
    }
    cout << ans[r] << 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模板库

暂无评论

发送评论 编辑评论


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