题解 | 牛客周赛 Round 165

A 小月的 DX 分

知识点:计数、加权求和

直接计算即可,$3a+2b+c$。

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

void solve() {
    int a, b, c, d, e;
    cin >> a >> b >> c >> d >> e;
    cout << a * 3 + b * 2 + c << endl;
}

B 小月的配对

知识点:枚举、取模

直接枚举每个位置 $i$,读取对应的排列值 $p_i$,判断
$$(i+p_i)\bmod k=0$$
是否成立。满足条件就把答案加一。

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

void solve() {
    int n, k, ans = 0;
    cin >> n >> k;
    for (int i = 1, x; i <= n; ++i) cin >> x, ans += (i + x) % k == 0;
    cout << ans << endl;
}

C 小月的程序

知识点:前缀性质、后缀性质、分类讨论

选择参数 $k$ 后,前 $k$ 个数会变成
$$a_k,a_{k-1},\dots,a_1.$$
要让这部分非递减,原数组前缀必须满足
$$a_1\ge a_2\ge\dots\ge a_k.$$
反转不会影响后缀,所以还需要
$$a_{k+1}\le a_{k+2}\le\dots\le a_n.$$
最后只剩反转部分与后缀的连接处,要求 $k<n$ 时满足 $a_1\le a_{k+1}$;当 $k=n$ 时没有连接处。

预处理 $p_i$ 表示前缀 $a_1,\dots,a_i$ 是否非递增,$s_i$ 表示后缀 $a_i,\dots,a_n$ 是否非递减。枚举每个 $k$,按上述条件判断即可。

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

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i];
    vector<bool> p(n + 1), s(n + 1);
    p[1] = s[n] = true;
    for (int i = 2; i <= n; ++i) p[i] = p[i - 1] && a[i - 1] >= a[i];
    for (int i = n - 1; i >= 1; --i) s[i] = s[i + 1] && a[i] <= a[i + 1];
    int ans = 0;
    for (int k = 1; k <= n; ++k) {
        bool ok = p[k];
        if (k < n) ok = ok && s[k + 1] && a[1] <= a[k + 1];
        ans += ok;
    }
    cout << ans << endl;
}

D 小月的删数

知识点:交替和、前缀和、按值分组

固定删除数值 $c$ 后,原数组会被这些被删除位置分成若干个连续保留段。每删除一个元素,后面保留元素在新数组中的奇偶下标都会翻转,因此这些保留段在交替和中的符号会交替变化。

采用下标从 $0$ 开始,定义交替前缀和
$$P_i=\sum_{j=0}^{i-1}(-1)^j a_j.$$
任意连续段 $[l,r)$ 的原始交替和可以由 $P_r-P_l$ 得到。扫描数值 $c$ 的所有出现位置时,维护上一段的起点,并在每次遇到被删位置后翻转当前段的符号,就能在线性时间内得到删除 $c$ 后的读数。所有不同的数值分别处理,所有位置总共只会被扫描一次。

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

constexpr int inf = 2e18 + 9;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n), p(n + 1);
    unordered_map<int, vector<int>> mp;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        p[i + 1] = p[i] + (i % 2 ? -a[i] : a[i]);
        mp[a[i]].push_back(i);
    }
    int ans = inf;
    for (auto [u, v] : mp) {
        int st = 0, op = 0, s = 0;
        for (auto x : v) {
            int t = p[x] - p[st];
            s += op ? -t : t;
            op ^= 1;
            st = x + 1;
        }
        ans = min(ans, llabs(s + (op ? p[st] - p[n] : p[n] - p[st])));
    }
    cout << ans << endl;
}

E 小月的折返颜色

知识点:并查集缩点、树上计数、按颜色分组

先把颜色相同的相邻节点用并查集合并。合并后,每个连通块内部颜色完全相同;把每个连通块缩成一个点,得到的仍然是一棵树,且相邻缩点颜色一定不同。

原树上一条路径恰好换色两次,当且仅当它在缩点树上经过
$$U\longrightarrow V\longrightarrow W$$
这两条边,并且两个端点连通块的颜色相同。固定中间点 $V$,枚举它的邻居连通块:若当前邻居连通块的颜色为 $q$、大小为 $s$,此前已经处理过的邻居中颜色为 $q$ 的节点总数为 $cnt_q$,则它与此前这些节点形成的合法点对数为
$$cnt_q\cdot s.$$
处理完当前邻居后再令 $cnt_q\mathrel{+}=s$。这样每个无序点对只会在它们路径的中间连通块处统计一次。

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

void solve() {
    int n;
    cin >> n;
    vector<int> c(n + 1);
    for (int i = 1; i <= n; ++i) cin >> c[i];

    DSU d(n);
    vector<pii> e(n - 1);
    for (auto &[u, v] : e) {
        cin >> u >> v;
        if (c[u] == c[v]) d.merge(u, v);
    }

    vector<vector<int>> g(n + 1);
    for (auto [u, v] : e) {
        u = d.find(u), v = d.find(v);
        if (u == v) continue;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    int ans = 0;
    vector<int> cnt(n + 1);
    for (int u = 1; u <= n; ++u) {
        for (int v : g[u]) {
            ans += cnt[c[v]] * d.size(v);
            cnt[c[v]] += d.size(v);
        }
        for (auto v : g[u]) cnt[c[v]] = 0;
    }
    cout << ans << endl;
}

F 小月的峰

知识点:二分答案、区间 DP、单调性

二分步幅上限 $d$。固定 $d$ 后,要求每一段相邻读数的差值都不超过 $d$,并且峰值位置 $p$ 满足

$$x_1<x_2<\dotsx_{p+1}>\dots>x_n.$$

从左向右维护上升前缀在第 $i$ 个位置可以取到的区间 $f_i=[L_i,R_i]$。若前一位置可取区间为 $[L_{i-1},R_{i-1}]$,那么严格上升且步幅不超过 $d$ 要求
$$x_i\in [L_{i-1}+1,R_{i-1}+d]\cap[l_i,r_i].$$
因此
$$L_i=\max(l_i,L_{i-1}+1),\qquad R_i=\min(r_i,R_{i-1}+d).$$

同理,从右向左计算下降后缀的可行区间。枚举峰值位置 $i$,只要上升前缀区间和下降后缀区间有交集,就存在一个合法峰值。

可行性关于 $d$ 单调:若某个 $d$ 可行,更大的步幅上限也一定可行。因此二分最小的 $d$。由于所有端点位于 $[-10^9,10^9]$,取 $d=2\times10^9$ 已经覆盖任意一对端点的距离;若此时仍不可行,则答案为 $-1$。

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

void solve() {
    int n;
    cin >> n;
    vector<pii> a(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i][0] >> a[i][1];

    auto check = [&](int d) {
        vector<pii> f(n + 1, {1, 0});
        f[1] = a[1];
        for (int i = 2; i < n; ++i) {
            f[i] = {max(a[i][0], f[i - 1][0] + 1), min(a[i][1], f[i - 1][1] + d)};
            if (f[i][0] > f[i][1]) break;
        }

        int l = a[n][0], r = a[n][1];
        for (int i = n - 1; i >= 2; --i) {
            l = max(a[i][0], l + 1);
            r = min(a[i][1], r + d);
            if (l > r) break;
            if (max(l, f[i][0]) <= min(r, f[i][1])) return true;
        }
        return false;
    };

    int l = 1, r = 2E9, ans = r;
    if (!check(r)) return cout << -1 << endl, void();
    while (l <= r) {
        int m = l + (r - l) / 2;
        if (check(m))
            ans = m, r = m - 1;
        else
            l = m + 1;
    }
    cout << l << 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;
}

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

暂无评论

发送评论 编辑评论


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