Codeforces Round #775 (Div. 2)

Contest Info


Practice Link

Solved A B C D E F
6 / 6 O O O O Ø Ø
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Reply


写完 \(A\) 发现忘记注册了,气得我怒开小号。顺便看看从 \(0\) 开始多少把能上紫。希望 \(cf\) 不要不识好歹,少出点数论和几何。

EF会补的,等我会了再说。
F在补了
这两天影小姐的池子开了,玩了两天原神,给影小姐弄了一身破铜烂铁,F刚补完。米哈游你罪该万死!
感觉不是很会这个F,好难啊,瞎写了一点,提供一点思路吧QwQ,教学流也太难了。

Solutions


A. Game

题意:
从点 \(1\) 跳到点 \(n\)\('0'\) 点不可跳,相邻 \('1'\) 点能直接走,最多只能跳一下,问最少跳多少步。

思路:
唯一的坑点就是只能跳一下,记录下首 \(0\) 和尾 \(0\) 就行。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n = rd();
    vector<int> vec;
    for (int i = 1; i <= n; ++i) {
        int t = rd();
        if (!t) vec.push_back(i);
    }
    int ans = 0;
    if (vec.size() == 0) ans = 0;
    else ans = vec[vec.size() - 1] - vec[0] + 2;
    printf("%d\n", ans);
}

int main() {
    int t = 1;
    t = rd();
    while (t--) solve();
    return 0;
}

B. Game of Ball Passing

题意:
\(n\)个人传球,告诉你每个人传球给别人的次数,问最少几个球让输入合法。

思路:
找到传球次数最多的人,看下其他人传球次数的总和。如果这个总和不小于最大值,这 \(n-1\) 个人就可以内部消耗,直到传球次数和最大值一致,就可以正好传完,只需要 \(1\) 个球。

其余情况则需要多个球了,因为最理想的情况就是每个人都传球给最多传球次数的人,剩下的就让他自己踢就行。

题目只问我们如何合法,所以我们不需要太关注传给谁,只关注传球的数量。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n = rd();
    ll sum = 0, maxx = 0;
    for (int i = 0; i < n; ++i) {
        int t = rd();
        if (t > maxx) maxx = t;
        sum += t;
    }
    if (maxx == 0) {
        puts("0");
        return;
    }
    ll ans = max(1ll, maxx - (sum - maxx));
    printf("%lld\n", ans);
}

int main() {
    int t = 1;
    t = rd();
    while (t--) solve();
    return 0;
}

C. Weird Sum

题意:

给你 \(n \cdot m\) 的矩阵和每个点的颜色,求相同颜色之间所有无序对的曼哈顿距离和。

思路:
\(dis_{u, v} = |x_u - x_v| + |y_u - y_v|\),我们可以发现曼哈顿距离中的 \(x\)\(y\) 是可以分开来计算的。那么我们把所有相同颜色格子的 \(x\)\(y\) 分开记录一下,从大到小排序一下,计算每个 \(x\) (\(y\)) 对于距离和的贡献。

\(x\) 为例。设总共有 \(n\) 个格子,当前遍历到排序后的第 \(i\)\(x\),那么这个 \(x_i\) 在计算曼哈顿距离时,对所有的 \(j\) (\(i < j \leq n\)),都需要计算一次 \(x_i - x_j\)。也就是说,\(x_i\) 需要被加 \(n - i\) 次。同理,需要被前面的数减去 \(i - 1\) 次。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector<int> X[100005], Y[100005];
void solve() {
    int n = rd(), m = rd();
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            int c = rd();
            X[c].push_back(i);
            Y[c].push_back(j);
        }
    }
    ll ans = 0;
    for (int i = 1; i <= 100000; ++i) sort(X[i].begin(), X[i].end(), greater<int>()), sort(Y[i].begin(), Y[i].end(), greater<int>());
    for (int i = 1; i <= 100000; ++i) {
        if (X[i].size() <= 1) continue;
        for (int j = 0; j < X[i].size(); ++j) {
            ans -= 1ll * j * X[i][j];
            ans += 1ll * (X[i].size() - j - 1) * X[i][j];

            ans -= 1ll * j * Y[i][j];
            ans += 1ll * (Y[i].size() - j - 1) * Y[i][j];
        }
    }
    printf("%lld\n", ans);
}

int main() {
    int t = 1;
    while (t--) solve();
    return 0;
}

D. Integral Array

题意:
如果对于一个数组中的任意两个数 \(x, y\),若 \(y \leq x\),那么 \(\lfloor \frac{x}{y} \rfloor\) 也存在于这个数组中,这个数组就是好数组。

现在给你一个数组和所有数的取值上界 \(c\),请你判断这个数组是否为好数组。

思路:
按照题意来写的话,枚举 \(x\)\(y\),虽然找一个数是否存在可以通过前缀和 \(O(1)\) 找,但枚举的 \(O(n^2)\) 就已经超时了。这时使用数论分块,找出所有的 \(\lfloor \frac{x}{y} \rfloor\) ,再判断除数 \(y\) 的取值区间中有没有数字存在即可。但这样的时间复杂度是 \(O(n\sqrt{c})\) 的,也无法接受。

那么我们正难则反,先把 \([1, max]\) 中所有不存在的值取出来(设为 \(b\) 数组),再和有的值(设为 \(a\) 数组)进行计算,看看是否存在 <\(b_i\), \(a_j\)>,使得 \([b_i \cdot a_j, (b_i + 1) \cdot a_j)\) 中的数在数组 \(a\) 中存在。这一步就相当于枚举所有存在的 \(\lfloor \frac{x}{y} \rfloor\) 和不存在的 \(y\) ,看看是否有非法的 \(x\) 存在。

因为上界 \(c\) 的存在,所以如果数组经过去重的操作之后,最坏情况下 \(1\) ~ \(c\) 都不存在,我们枚举的时候,及时 break,枚举的次数就是 \(c + \frac{c}{2} + \cdots + \frac{c}{c}\),这就是一个调和级数了,所以时间复杂度就是 \(O(clog_c)\),可以通过。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n = rd(), c = rd();
    vector<int> a(n), pre(c + 1);
    for (int i = 0; i < n; ++i) {
        a[i] = rd();
    }
    sort(a.begin(), a.end());
    a.erase(unique(a.begin(), a.end()), a.end());
    int now = 1;
    if (a[0] != 1) {
        puts("No");
        return;
    }
    vector<int> notHave;
    for (auto v : a) {
        pre[v]++;
        while (now < v) {
            now++;
            if (now != v) {
                notHave.push_back(now);
            }
        }
    }
    for (int i = 1; i <= c; ++i) pre[i] += pre[i - 1];
    for (auto u : notHave) {
        for (auto v : a) {
            ll l = 1ll * u * v;
            if (l > c) break;
            ll r = min(1ll * (u + 1) * v - 1, (ll)c);
            if (pre[r] - pre[l - 1] == 0) continue;
            else {
                puts("No");
                return;
            }
        }
    }
    puts("Yes");
}

int main() {
    int t = 1;
    t = rd();
    while (t--) solve();
    return 0;
}

E. Tyler and Strings

题意:
给定序列 \(s\)\(t\),现对 \(s\) 进行重新排列,问能排列出多少种本质不同的序列 \(s'\) ,使得 \(s'\) 的字典序小于 \(t\)

思路:
前置知识:设数字 \(i\) 出现的数量为 \(cnt_i\) ,数字的最大值为 \(k\) ,那么将这个序列排列成所有本质不同的序列的方案是

\[\frac{(cnt_1 + cnt_2 + \cdots + cnt_k)!}{cnt_1! \cdot cnt_2! \cdots cnt_k!} \]

相当于对所有数字进行全排列,然后去掉每个数字各自的全排列对方案的重复影响。

要让字典序小,假设当前在第 \(i\) 位,那么 \(s'\)\(t\) 的前缀要一致,并且 \(s'_i < t_i\),后面就可以任意排列了。所以我们遍历一遍 \(t\) ,查询 \(s\) 中有多少数字小于 \(t_i\) ,挨个放上去,再计算 \(\frac{(cnt_1 + cnt_2 + \cdots + (cnt_x - 1) + \cdots + cnt_k)!}{cnt_1! \cdot cnt_2! \cdots (cnt_x - 1)! \cdots cnt_k!}\) ,把这些贡献加入答案中,最后再把这一位填上 \(t_i\) ,把相应的变量值更改一下,接着遍历下去就行。注意判断当前 \(s\) 中已经没有 \(t_i\) 的情况和 \(n < m\) 的情况。然后注意一下 \(s'\) 可以排列成 \(t\) 的前缀串的情况。

但这样我们需要枚举比 \(t_i\) 小的数字,时间复杂度达到了 \(O(min(n, m) \cdot k)\)。我们再考虑遍历到 \(t_i\) 时产生的贡献,设当前的\(\frac{(cnt_1 + cnt_2 + \cdots + cnt_k - 1)!}{cnt_1! \cdot cnt_2! \cdots cnt_k!}\)\(now\) ,那么产生的贡献就是 \(now \cdot cnt_1 + now \cdot cnt_2 + \cdots + now \cdot cnt_{t_i - 1}\),其实就相当于要维护一个数量的前缀和,用一个树状数组维护即可。​

我们再预处理个逆元,时间复杂度就是 \(O(klog_k)\)

不知道为什么场上写不出来,感觉自己变笨了。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

const ll mod = 998244353;
ll f[200005], inv[200005];

ll powmod(ll a, ll b) {
    ll res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

struct Bit {
    int n;
    vector<int> bit;
    void init(int N) {
        n = N;
        bit.clear();
        bit.resize(N + 1, 0);
    }
    int lowbit(int x) {
        return x & -x;
    }
    void add(int i, int x) {
        while (i <= n) {
            bit[i] += x;
            i += lowbit(i);
        }
    }
    int query(int i) {
        int ans = 0;
        while (i) {
            ans += bit[i];
            i -= lowbit(i);
        }
        return ans;
    }
}b;

void solve() {
    int n = rd(), m = rd();
    vector<int> s(n + 1), t(m + 1), cnt(200005);
    b.init(200000);
    for (int i = 1; i <= n; ++i) s[i] = rd(), b.add(s[i], 1), cnt[ s[i] ]++;
    for (int i = 1; i <= m; ++i) t[i] = rd();
    ll nowInv = 1;
    for (int i = 1; i <= 200000; ++i) nowInv = nowInv * inv[ cnt[i] ] % mod;
    ll ans = 0;
    int exAdd = 1;
    for (int i = 1; i <= n; ++i) {
        if (i > m) break;
        ans += f[n - i] * b.query(t[i] - 1) % mod * nowInv % mod;
        if (cnt[ t[i] ] == 0) {
            exAdd = 0;
            break;
        }
        nowInv = nowInv * cnt[ t[i] ] % mod;
        cnt[ t[i] ]--;
        b.add(t[i], -1);
    }
    if (n >= m) exAdd = 0;
    ans = (ans + exAdd) % mod;
    printf("%lld\n", ans);
}

int main() {
    f[0] = 1;
    for (int i = 1; i <= 200000; ++i) f[i] = f[i - 1] * i % mod;
    inv[200000] = powmod(f[200000], mod - 2);
    for (int i = 200000; i >= 1; --i) inv[i - 1] = inv[i] * i % mod;
    int t = 1;
    while (t--) solve();
    return 0;
}

F. Serious Business

题意:
给定 \(3 \times n\) 的矩阵,你要从点 \((1, 1)\) 走到 点 \((3, n)\) ,每次只能向下或者向右走,权值和为一路的点权和。一开始第二行所有点都不能走,你有 \(q\) 次操作,你可以选择执行其中若干次操作,使你拥有的权值减少 \(k_i\) ,让第二行 \([l_i, r_i]\) 成为可走点。问权值最大为多少。

思路:
理解一下题意,就是第一行连续走几步,然后向下走一步,再在第二行连续走几步,再向下走一步,最后走到底就行。假设第一行走到点 \((1, i)\) ,第二行走到点 \((2, j)\) ,那么权值花费就是

\[\sum_{k = 1}^{i}a_{1,k} + \sum_{k = i}^{j}a_{2,k} + \sum_{k = j}^{n}a_{3,k} + cost_{i, j} \]

其中 \(cost_{i, j}\) 代表将第二行的 \([i,j]\) 区间内的各自都变为可走的最小花费。

一看数据范围,估计要遍历 \(i, j\) 的其中一维,然后快速地从决策集合中取出最优解。

首先点 \(i, j\) 必须都在操作区间内,这提示我们要遍历所有的操作区间,在操作区间内找到所有的合法转移,那么 \(i\) 应该代表一个最优前缀, \(j\) 代表一个最优后缀,而且第一行和第三行的权值也确实是相应的前后缀,只剩下第二行的权值是一个区间权值和,而且对于转移有影响。那么区间权值和不就是前缀相减吗?

这里有个费用提前的 \(trick\) ,即在所有点 \(i\) 的状态中减去第二行的前缀和,在所有点 \(j\) 的前缀和中加上第二行的前缀和。这样前缀还是只有一个状态,并且我就可以直接在后缀中取出最大值,保证他是利用当前操作区间时的最优解。

我们发现前缀不受操作的影响,所以尝试从后往前遍历,进行转移。

假设当前遍历到点 \(ql\) ,我们再遍历所有以点 \(ql\) 作为左区间的操作,如果点 \(i\) 在当前区间,那么求出最大值后,减去操作的花费 \(k\) 就是答案了。如果要进行转移,如何影响前面的操作区间呢?因为每行走的格子是连续的,所以点 \(i - 1\) 必须能用到你转移过来的后缀才行,所以我们将区间最优后缀取出来,减去当前区间的花费 \(k\) ,和之前计算出来的 \(i - 1\) 的后缀取个最大值即可。

以上操作就可以用线段树来维护了。注意合并的时候要先用左区间的前缀和右区间的后缀合并,更新最优解,不然先对前缀后进行操作,可能造成左区间的后缀和右区间的前缀进行合并,进行非法转移。

#include <bits/stdc++.h>

using namespace std;

inline int rd() {
    int f = 0; int x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    if (f) x = -x;
    return x;
}

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;

const int N = 5e5 + 7;

ll f[2][N], a[3][N];
int n, q;
vector< pair<int, ll> > Q[N];

#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid (l + r >> 1)

struct Seg {
    struct Tree {
        ll maxx, pre, suf;
    } t[N << 2];

    Tree merge(Tree a, Tree b) {
        a.maxx = max({a.maxx, b.maxx, a.pre + b.suf});
        a.pre = max(a.pre, b.pre);
        a.suf = max(a.suf, b.suf);
        return a;
    }

    void modify(int id, int l, int r, int p) {
        if (l == r) {
            t[id] = {f[0][l] + f[1][l], f[0][l], f[1][l]};
            return;
        }
        if (p <= mid) modify(ls, l, mid, p);
        else modify(rs, mid + 1, r, p);
        t[id] = merge(t[ls], t[rs]);
    }

    Tree query(int id, int l, int r, int ql, int qr) {
        if (r < ql || l > qr) return {-INF, -INF, -INF};
        if (ql <= l && r <= qr) return t[id];
        return merge(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr));
    }

    pair<ll, ll> getMax(int ql, int qr) {
        Tree tmp = query(1, 1, n, ql, qr);
        return {tmp.maxx, tmp.suf};
    }

} seg;

#undef mid

void solve() {
    n = rd(), q = rd();
    for (int dd = 0; dd < 3; ++dd) {
        for (int i = 1; i <= n; ++i) {
            a[dd][i] = rd();
        }
    }
    for (int i = 1; i <= n; ++i) {
        f[0][i] = f[0][i - 1] + a[0][i] - a[1][i - 1];
        f[1][i] = f[1][i - 1] + a[1][i];
    }
    ll tmp = 0;
    for (int i = n; i >= 1; --i) {
        tmp += a[2][i];
        f[1][i] += tmp;
    }
    
    for (int i = 1; i <= q; ++i) {
        int l = rd(), r = rd(), k = rd();
        Q[l].push_back({r, k});
    }
    ll ans = -INF;
    for (int ql = n; ql >= 1; --ql) {
        seg.modify(1, 1, n, ql);
        for (auto [qr, k] : Q[ql]) {
            auto [maxx, maxSuf] = seg.getMax(ql, qr);
            ans = max(ans, maxx - k);
            f[1][ql - 1] = max(f[1][ql - 1], maxSuf - k);
        }
    }
    printf("%lld\n", ans);
}

int main() {
    int t = 1;
    while (t--) solve();
    return 0;
}
posted @ 2022-03-07 00:24  stff577  阅读(464)  评论(4编辑  收藏  举报