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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM