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 跳到点 n0 点不可跳,相邻 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个人传球,告诉你每个人传球给别人的次数,问最少几个球让输入合法。

思路:
找到传球次数最多的人,看下其他人传球次数的总和。如果这个总和不小于最大值,这 n1 个人就可以内部消耗,直到传球次数和最大值一致,就可以正好传完,只需要 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

题意:

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

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

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

#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,若 yx,那么 xy 也存在于这个数组中,这个数组就是好数组。

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

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

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

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

#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

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

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

(cnt1+cnt2++cntk)!cnt1!cnt2!cntk!

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

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

但这样我们需要枚举比 ti 小的数字,时间复杂度达到了 O(min(n,m)k)。我们再考虑遍历到 ti 时产生的贡献,设当前的(cnt1+cnt2++cntk1)!cnt1!cnt2!cntk!now ,那么产生的贡献就是 nowcnt1+nowcnt2++nowcntti1,其实就相当于要维护一个数量的前缀和,用一个树状数组维护即可。​

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

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

#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×n 的矩阵,你要从点 (1,1) 走到 点 (3,n) ,每次只能向下或者向右走,权值和为一路的点权和。一开始第二行所有点都不能走,你有 q 次操作,你可以选择执行其中若干次操作,使你拥有的权值减少 ki ,让第二行 [li,ri] 成为可走点。问权值最大为多少。

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

k=1ia1,k+k=ija2,k+k=jna3,k+costi,j

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

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

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

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

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

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

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

#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 @   stff577  阅读(483)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2021-03-07 [2020 ICPC 上海 C] Sum of Log
2021-03-07 Codeforces Round #705 (Div. 2)
点击右上角即可分享
微信分享提示