Educational Codeforces Round 183 (Rated for Div. 2) 补题记录

Educational Codeforces Round 183 (Rated for Div. 2) 补题记录

D - Inversion Value of a Permutation

原题链接

题目大意:

排列的逆序值为这个排列中存在逆序对的子区间的个数。

构造一个 \(n\) 个数的排列,使排列的逆序值恰好等于 \(k\)

思路:

首先考虑如何计算答案。假设存在一组逆序对,\((p_i, p_j)\) \((1 \leq i, j \leq n)\) 其中 \(i + 1 = j\),我们可以发现这一组逆序对的贡献应该是 \(i\)。并且对于任意的逆序对 \((p_k, p_j)\) 其中 \(k < i\),他所代表的区间都已经被 \((p_i, p_j)\) 的贡献计算。因此我们得出结论:只需要统计相邻逆序对的贡献之和,即为排列的逆序值。

可以发现如果直接构造逆序值恰好为 \(k\) 的排列,难度很大。正难则反,我们可以考虑构造一个有 \(k' = \frac{n (n - 1)}{2} - k\) 个不存在逆序对的子区间的排列。

同时,我们知道:对于一个长度为 \(len\) 的连续递增子数组,他对 \(k'\) 的贡献为 \(\frac{len (len - 1)}{2}\)。因此便可以进一步转化为用 \(n\) 个数构造 \(m\) 个连续递增的子数组,其中第 \(i\) 个子数组的长度为 \(len_i\)。且存在:

\[\sum_{i = 1}^{m} \frac{len_i (len_i - 1)}{2} = k' \]

接着我们再进一步考虑如何构造最终答案,其实很简单。对于一个排列 \(p = {1, 2, \dots, n}\) 我们只需要把它分成 \(m\) 组且符合上述等式,再将其倒序排列然后输出即可。

最后,我们要解决的就只剩下答案的存在性了。也就是我们能否用 \(n\) 个数组成一个存在 \(k'\) 个不存在逆序对的区间的排列。可以考虑用 \(dp\) 来预处理。用 \(dp[i][j]\) 表示使用 \(i\) 个数字构造 \(j\) 个连续子数组的可行性,那么转移方程便是:

\[dp[i][j] = dp[i][j] \ || \ dp[i - 1][j - len] \]

其中,\(len\) 代表当前要构造一个长度为 \(len\) 的子数组。

code:

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

#define endl '\n'
#define i64 long long

const int N = 40, M = N * (N - 1) / 2;
int f[N][M], pre[N][M];

void init() {
    f[0][0] = 1;
    for (int i = 1; i < N; i ++ ) {
        for (int j = 0; j < M; j ++ ) {
            for (int len = 1; i - len >= 0 && j - len * (len - 1) / 2 >= 0; len ++ ) {
                if (f[i - len][j - len * (len - 1) / 2]) {
                    f[i][j] = 1;
                    pre[i][j] = len;
                }
            }
        }
    }
}

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

    k = n * (n - 1) / 2 - k;
    if (!f[n][k]) {
        cout << "0\n";
        return;
    }

    int nn = n, kk = k, last = 1;
    vector<int> ans;
    while (pre[nn][kk]) {
        int len = pre[nn][kk];
        for (int i = last + len - 1; i >= last; i -- ) {
            ans.emplace_back(i);
        }
        last = last + len;
        nn -= len, kk -= len * (len - 1) / 2;
    }

    for (int i = n - 1; i >= 0; i -- ) {
        cout << ans[i] << " \n"[i == 0];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    init();
    
    int t; cin >> t;
    while (t -- ) MuBai();

    return 0;
}

E - Predicting Popularity

原题链接

题目大意:

给出两个整数 \(ac\)\(dr\) 表示一部电影的动作和剧情评分。

\(n\) 个观众,第 \(i\) 个观众的动作偏好为 \(a_i\) ,剧情偏好为 \(d_i\) ,假设现已观看电影的人数为 \(p\)。当满足如下条件时,第 \(i\) 位观众才会观看电影:

\[max(a_i - ac, 0) + max(d_i - dr, 0) \leq p \]

接着,有 \(m\) 次修改,第 \(i\) 次修改会改变第 \(k_i\) 个人的偏好,问每次修改过后最多有多少人会观看电影。

思路:

首先考虑如何计算答案:

由题意可知,一位观众只有当 \(max(a - ac, 0) + max(d - dr, 0) <= p\) 时才会观看电影。

同时,我们定义 \(cnt[i]\) 为当 \(p = i\) 时,观看电影的观众数量。容易得知,当 \(cnt[i] < i\) 时,\(p \geq i\) 都是无效的答案。

因此只需要找到从 \(i = 1\) 开始最长的 \(cnt[i] \geq i\) 的前缀长度便是所求答案。

然后考虑如何维护修改:

对于上述所求可以变式为维护 \(cnt[i] - i\) 的值,那么 \(cnt[i] - i \geq 0\) 的最大前缀长度便是答案。

假设一位观众的阈值为 \(x = max(a - ac, 0) + max(d - dr, 0)\) 那么他对于 \([cnt[x], cnt[inf]]\) 都存在贡献。因此修改一位观众的阈值,需要减去区间 \([x, inf)\) 的贡献,再在新的贡献区间 \([x_, inf)\) 加上贡献。

区间修改,考虑线段树维护。

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

#define endl '\n'
#define i64 long long

#define lson node << 1
#define rson node << 1 | 1

template <class Info, class Tag>
struct LazySegmentTree {
    struct Node {
        int l, r;
        Info info;
        Tag tag;
    };

    vector<Node> tree;

    LazySegmentTree(int n) {
        init(n);
    }

    void init(int n) {
        tree.resize(4 << __lg(n));

        function<void(int, int, int)> build = [&](int node, int l, int r) {
            Node& cur = tree[node];
            cur.l = l, cur.r = r;
            if (l == r) {
                cur.info.mi = -l;
                return;
            }
            int mid = (l + r) >> 1;
            build(lson, l, mid);
            build(rson, mid + 1, r);
            maintain(node);
        };

        build(1, 0, n - 1);
    }

    void maintain(int node) {
        Node& cur = tree[node];
        cur.info = tree[lson].info + tree[rson].info;
    }

    void apply(int node, const Tag& tag) {
        Node& cur = tree[node];
        cur.info = cur.info + tag;
        cur.tag = cur.tag + tag;
    }

    void spread(int node) {
        Node& cur = tree[node];
        if (!cur.tag.isTag()) {
            return;
        }
        apply(lson, cur.tag);
        apply(rson, cur.tag);
        cur.tag.clear();
    }

    void rangeApply(int node, int l, int r, const Tag& tag) {
        Node& cur = tree[node];
        if (l <= cur.l && cur.r <= r) {
            return apply(node, tag);
        }
        spread(node);
        int mid = (cur.l + cur.r) >> 1;
        if (l <= mid) {
            rangeApply(lson, l, r, tag);
        }
        if (r > mid) {
            rangeApply(rson, l, r, tag);
        }
        maintain(node);
    }

    void rangeApply(int l, int r, const Tag& tag) {
        rangeApply(1, l, r, tag);
    }

    template <class T>
    int findFirst(int node, T&& check) {
        Node& cur = tree[node];
        if (!check(cur.info)) {
            return -1;
        }
        if (cur.l == cur.r) {
            return cur.l;
        }
        spread(node);
        int res = findFirst(lson, check);
        if (res == -1) res = findFirst(rson, check);
        return res;
    }

    template <class T>
    int findFirst(T&& check) {
        return findFirst(1, check);
    }
};

struct Info {
    int mi = INT_MAX;
};

struct Tag {
    int add = 0;
    bool isTag() { return add != 0; }
    void clear() { add = 0; }
};

Info operator + (const Info& l, const Info& r) {
    return { min(l.mi, r.mi) };
}

Info operator + (const Info& info, const Tag& tag) {
    return { info.mi + tag.add };
}

Tag operator + (const Tag& x, const Tag& y) {
    return { x.add + y.add };
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    int ac, dr, n, m;
    cin >> ac >> dr >> n;

    vector<int> a(n + 1), d(n + 1), t(n + 1);
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) {
        cin >> d[i];
        t[i] = max(a[i] - ac, 0) + max(d[i] - dr, 0);
    }

    LazySegmentTree<Info, Tag> seg_tree(n + 1);
    for (int i = 1; i <= n; i ++ ) {
        if (t[i] <= n) seg_tree.rangeApply(t[i], n, {1});
    }

    cin >> m;
    for (int i = 1, p; i <= m; i ++ ) {
        cin >> p >> a[p] >> d[p];

        if (t[p] <= n) seg_tree.rangeApply(t[p], n, {-1});
        t[p] = max(a[p] - ac, 0) + max(d[p] - dr, 0);
        if (t[p] <= n) seg_tree.rangeApply(t[p], n, {1});

        cout << seg_tree.findFirst([&](const Info& p) {
            return p.mi <= 0;
        }) << endl;
    }

    return 0;
}

F - Long Journey

原题链接

题目大意:

给定两个整数 \(n\)\(m\),以及两个数组 \(a,b\)

\([0, m]\) 的条形区间中,当回合 \(i + kn,k \in [0, 1, 2 \dots ]\) 结束时,所有符合 \(x\) \(mod\) \(a_i \equiv b_i\) 的单位格将触发陷阱。

在不踩到陷阱的前提下,至少需要多少步才能从 \(0\) 走到 \(m\)

思路:

先考虑模型转化,每回合先行动(走一格或停在本格),再按照规则激活陷阱。注意到,第 \(x\) 回合应用的规则只与 \(x \ mod \ n\) 有关,因此可以把回合数压缩到 \([0, n)\) 的区间进行处理。

而第 \(i\) 格在第 \(x\) 回合是否可达取决于 \(i\)\(a[x \ mod \ n]\) 的余数,因此让 \(i\)\(lcm(a[0], a[1], \dots, a[n - 1])\) 取模就可以包含所有信息。此时:

\[ i \ mod \ lcm \equiv b_x(mod \ a_x) \Leftrightarrow i \equiv b_x(mod \ a_x) \]

我们发现,题目中的 \(m \leq 10^{18}\) 数据非常大,因此考虑用倍增来拼凑这个 \(m\),我们用 \(dp[i][j][p]\) 来表示当前应用 \(i = x \ mod \ n\) 条规则,位于 \(j \ mod \ lcm\) 单位格 \(2^p\) 单位时间后最多能走的步数。存在如下转移:

\[dp[i][j][p] = dp[i][j][p - 1] + dp[(i + (1 << (p - 1))) \ mod \ n][(j + dp[i][j][p - 1]) \ mod \ lcm][p - 1] \]

最后,只需要特判 \(dp[0][0][maxP - 1] > m\) 是否成立然后计算答案即可。

code:

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

#define endl '\n'
#define i64 long long

const int N = 15, M = 50, LCM = 2520;
i64 dp[N][LCM][M];

void MuBai() {
    i64 n, m;
    cin >> n >> m;

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

    for (int i = 0; i < n; i ++ ) {
        for (int j = 0; j < LCM; j ++ ) {
            if ((j + 1) % a[i] != b[i]) {
                dp[i][j][0] = 1;
            }
            else {
                dp[i][j][0] = 0;
            }
        }
    }

    for (int p = 1; p < M; p ++ ) {
        for (int i = 0; i < n; i ++ ) {
            for (int j = 0; j < LCM; j ++ ) {
                dp[i][j][p] = dp[i][j][p - 1] + dp[(i + (1ll << (p - 1))) % n][(j + dp[i][j][p - 1]) % LCM][p - 1];
            }
        }
    }

    if (dp[0][0][M - 1] < m) {
        cout << "-1\n";
        return;
    }
    
    i64 x = 0, y = 0;
    for (int p = M - 1; p >= 0; p -- ) {
        if (y + dp[x % n][y % LCM][p] < m) {
            y += dp[x % n][y % LCM][p];
            x += (1ll << p);
        }
    }

    cout << x + 1 << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    int t; cin >> t;
    while (t -- ) MuBai();

    return 0;
}
posted @ 2025-11-26 09:41  算法蒟蒻沐小白  阅读(30)  评论(0)    收藏  举报