Codeforces Round 988 (Div. 3) 補題記錄(A~G)

yhbqwq發表於2024-11-18

總結:賽時一題不會,賽後光速 AK

A

namespace Triple_Light {

int a[N];
void run() {
    int T = read();
    while (T--) {
        int n = read();
        int buc[30] = {0};
        for (int i = 1; i <= n; ++i) {
            int x = read();
            ++buc[x];
        }
        int cnt = 0;
        for (int i = 1; i <= n; ++i) {
            cnt += buc[i] / 2;
        }
        cout << cnt << '\n';
    }
} }

B

首先可以知道 \(n\times m=k-2\),又因為 \(k-2\) 很小,所以直接暴力分解 \(k-2\) 的因數,判斷是否可以拆分為 \(h\times w=k-2\) 且滿足 \(a\) 中恰好存在 \(h\)\(w\) 即可。特殊的,需要特判 \(h=w\) 的情況。

總時間複雜度為 \(O(k)\)

namespace Triple_Light {

int a[N], vis[N];
void run() {
    int T = read();
    while (T--) {
        int n = read();
        for (int i = 1; i <= n; ++i) {
            vis[i] = 0;
        }
        for (int i = 1; i <= n; ++i) {
            a[i] = read();
            ++vis[a[i]];
        }
        int kx = n - 2;
        for (int i = 1; i * i <= kx; ++i) {
            if (kx % i == 0) {
                int a = kx / i, b = i;
                if (a == b) {
                    if (vis[a] >= 2) {
                        cout << a << ' ' << b << '\n';
                        break;
                    }
                } else {
                    if (vis[a] && vis[b]) {
                        cout << a << ' ' << b << '\n';
                        break;
                    }
                }
            }
        }
    }
} }

C

頂好玩的構造題。首先大於 \(4\) 的偶數肯定是合數,也就是說一堆奇數和偶數肯定是滿足條件的。問題是奇數和偶數之間的地方,考慮讓其的和可以被 \(3\) 整除,直接令奇數最後一個是最大的奇數,然後暴力列舉合法的可以銜接的偶數即可。對於 \(n\) 很小的情況可以直接暴搜全排列,總時間複雜度為 \(O(n)\)

namespace Triple_Light {

int a[N], vis[N];
void run() {
    int T = read();
    while (T--) {
        int n = read();
        for (int i = 1; i <= n; ++i) {
            vis[i] = 0;
        }
        for (int i = 1; i <= n; ++i) {
            a[i] = read();
            ++vis[a[i]];
        }
        int kx = n - 2;
        for (int i = 1; i * i <= kx; ++i) {
            if (kx % i == 0) {
                int a = kx / i, b = i;
                if (a == b) {
                    if (vis[a] >= 2) {
                        cout << a << ' ' << b << '\n';
                        break;
                    }
                } else {
                    if (vis[a] && vis[b]) {
                        cout << a << ' ' << b << '\n';
                        break;
                    }
                }
            }
        }
    }
} }

D

首先若跨越了第 \(i-1\) 個障礙,則肯定能夠到達 \(l_i-1\)。如果不選能從 \(l_i-1\) 直接到達 \(r_i+1\) 那麼肯定不選,否則暴力列舉當前所有可以選擇的裝備並貪心的選擇可以移動距離最大的裝備,直到當前可以從 \(l_i-1\) 跳到 \(r_i+1\) 為止。維護當前可選的裝備可以用堆維護,時間複雜度為 \(O(n\log n)\)

namespace Triple_Light {

int l[N], r[N], x[N], v[N];
void run() {
    int T = read();
    while (T--) {
        int n = read(), m = read(), L = read();
        for (int i = 1; i <= n; ++i) {
            l[i] = read();
            r[i] = read();
        }
        for (int i = 1; i <= m; ++i) {
            x[i] = read();
            v[i] = read();
        }
        int pos = 1;
        priority_queue<int> q;
        int now = 1, cnt = 0, ok = 1;
        for (int i = 1; i <= n; ++i) {
            while (pos <= m && x[pos] < l[i]) {
                q.push(v[pos]);
                ++pos;
            }
            int len = r[i] - l[i] + 2;
            if (now < len) {
                while (q.size()) {
                    int t = q.top();
                    q.pop();
                    now += t;
                    ++cnt;
                    if (now >= len) {
                        break;
                    }
                }
                if (now < len) {
                    ok = 0;
                    break;
                }
            }
        }
        if (ok) {
            cout << cnt << '\n';
        } else {
            cout << "-1\n";
        }
    }
} }

E

首先考慮手摸一下尋找線索,可以發現若每一次都按照順序詢問一個字首,則在一開始詢問到一堆 \(0\) 之後在 \(i\) 位置得到了一個非零數 \(x\),則:

  • \(a_x=1\)
  • \(a\) 陣列中存在一個長度為 \(x\) 的全 \(1\) 字首。
  • 其餘在 \([1,x]\) 字首內的位置的值全都為 \(0\)

然後對於後面仍然詢問字首,若當前詢問到字首和上一次詢問字首的答案相同,則當前位結尾對答案沒有貢獻,即當前位為 \(0\),否則因為前面一定存在 \(0\)(否則第一次不能詢問出非 \(0\) 值),當前位對答案產生貢獻,則當前位為 \(1\)

特殊的,若沒有找到一個非 \(0\) 的字首,則輸出 IMPOSSIBLE

總時間複雜度為 \(O(n)\),詢問 \(n-1\) 次,符合題目條件。

namespace Triple_Light {

int a[N];
void run() {
    int T = read();
    while (T--) {
        int n = read(), pos = 1;
        for (int i = 1; i <= n; ++i) {
            a[i] = -1;
        }
        int la = 0;
        for (int i = 2; i <= n; ++i) {
            cout << "? " << pos << ' ' << i << endl;
            int o ; cin >> o;
            int to = o;
            if (o != la) {
                if (!la) {
                    o -= la;
                    a[i] = 1;
                    int j, k;
                    for (j = i - 1, k = 1; k <= o; --j, ++k) {
                        a[j] = 0;
                    }
                    for (; j >= pos; --j) {
                        a[j] = 1;
                    }
                    la = to;
                } else {
                    a[i] = 1;
                    la = to;
                }
            } else if (la) {
                // cout << "qwq " << i << '\n';
                a[i] = 0;
            }
        }
        if (count(a + 1, a + n + 1, -1)) {
            cout << "! IMPOSSIBLE" << endl;
        } else {
            cout << "! ";
            for (int i = 1; i <= n; ++i) {
                cout << a[i];
            }
            cout << endl;
        }
    }
} }

F

笑點解析:已被刪除

考慮經典套路,二分一個答案 \(x\) 判斷其是否合法。考慮二分完之後計算每一個怪物可以在攻擊 \(x\) 輪之後去世的區間 \([L_i,R_i]\),然後在數軸上建立掃描線模型,設 \((x,o)\) 表示 \(x\) 位置中線段數量會增加 \(o\)(若 \(o\) 為負數則為減少 \(-o\)),因此只需要新增 \((L_i,1)\)\((R_i+1,-1)\)。然後對掃描線按照座標從小到大排序並判斷是否存在一個字首滿足該字首內所有 \(o\) 的和超過了 \(k\) 即可。

總時間複雜度為 \(O(n\log n)\),可以透過。

namespace ttq012 {
 
int h[N], x[N];
void run() {
    int T = read();
    while (T--) {
        int n = read(), m = read(), k = read();
        for (int i = 1; i <= n; ++i) {
            h[i] = read();
        }
        for (int i = 1; i <= n; ++i) {
            x[i] = read();
        }
        int l = 1, r = inf, best = -1;
        while (l <= r) {
            int mid = l + r >> 1;
            vector<pair<int, int>> event;
            for (int i = 1; i <= n; ++i) {
                int pwk = (h[i] + mid - 1) / mid;
                if (pwk <= m) {
                    int L = x[i] - (m - pwk), R = x[i] + (m - pwk);
                    event.eb(L, 1), event.eb(R + 1, -1);
                }
            }
            sort(event.begin(), event.end(), [&](auto l, auto r) {
                return l.first < r.first || l.first == r.first && l.second < r.second;
            }) ;
            int pref = 0, ok = 0;
            for (auto &[pos, vx] : event) {
                if ((pref += vx) >= k) {
                    ok = 1;
                    break;
                }
            }
            if (ok) {
                best = mid, r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        cout << best << '\n';
    }
} }

G

看到 \(\gcd\neq 1\) 可以快速想到莫比烏斯函式容斥答案,於是設 \(f_i\) 表示從第 \(1\) 個數移動到第 \(i\) 個數的不同路徑數量,顯然有 dp 轉移式:

\[f_i=\sum\limits_{j=1}^{i-1}[\gcd(a_i,a_j)\neq 1]f_j \]

但是直接轉移時間複雜度為 \(O(n^2\log n)\),顯然無法透過。考慮設 \(g_i\) 表示在值域上,當前為 \(i\) 的倍數的 \(a_i\) 所代表的 \(f_i\) 的值的和,則可以使用莫比烏斯函式對答案容斥,有:

  • \(f_1=1\)
  • \(f_i=-\sum\limits_{x\mid a_i}\mu(x)g_x\)\(i>1\)
  • \(g_x\leftarrow g_x+f_i\)\(x\mid a_i\)

尤拉線性篩出 \(\mu\) 函式的值,然後直接 dp 時間複雜度為 \(O(n^\frac{3}{2})\),使用 Pollard-Rho 分解質因數可以最佳化到 \(O(n^\frac{5}{4})\)。但是可以一遍埃篩得到每一個數的因數,做到 \(O(n\log n)\)

namespace ttq012 {
 
int a[N], mu[N], idx, pr[N], isp[N];
void sieve(int n) {
    isp[1] = 1, mu[1] = 1;
    for (int i = 2; i < n; ++i) {
        if (!isp[i]) {
            pr[++idx] = i, mu[i] = -1;
        }
        for (int j = 1; j <= idx && i * pr[j] < n; ++j) {
            int k = i * pr[j];
            isp[k] = 1;
            if (i % pr[j] == 0) {
                mu[k] = 0;
                break;
            } else {
                mu[k] = -mu[i];
            }
        }
    }
}
int f[N], g[N];
void run() {
    sieve(N);
    int n = read();
    for (int i = 1; i <= n; ++i) {
        a[i] = read();
    }
    for (int i = 1; i <= n; ++i) {
        vector<int> fact;
        for (int j = 2; j * j <= a[i]; ++j) {
            if (a[i] % j == 0) {
                fact.eb(j);
                if (j * j != a[i]) {
                    fact.eb(a[i] / j);
                }
            }
        }
        fact.eb(a[i]);
        if (i == 1) {
            f[i] = 1;
        } else {
            for (auto &x : fact) {
                f[i] = (f[i] - mu[x] * g[x] % mod + mod) % mod;
            }
        }
        for (auto &x : fact) {
            g[x] = (g[x] + f[i]) % mod;
        }
    }
    cout << f[n] << '\n';
} }

相關文章