1. 程式人生 > 其它 >Codeforces Round #626 (Div. 1)

Codeforces Round #626 (Div. 1)

Codeforces Round #626 (Div. 1)

A

B

對每一位分別計算,只考慮 \(x,y\) 的前 \(k\) 位,若 \(x+y\) 的第 \(k\) 位為 \(1\),則 \(x+y\in[2^k,2^{k+1})\cup[2^k+2^{k+1},2^{k+2})\),排序後雙指標統計即可

C

結論題,但大多數題解的證明有問題,建議看官方題解或[中文版題解](題解 CF1322C [Instant Noodles] - 老年退役選手 QwQcOrZ 的小窩 - 洛谷部落格 (luogu.com.cn)),享受優美嚴謹的證明

D

\(f_{i,j}\) 表示有 \(j\)

個等級為 \(i\) 的選手的最大收益(當前最大等級為 \(i\))。加入一個等級 \(l_i\) 的人有兩種轉移:1. 推一遍 \(f_{l_i,*}\);2. 把 \(l_i\) 推向 \(l_{i+1},l_{i+2}\dots\)。在這樣的轉移中,如果後加入的 \(i<j,l_j<l_i\),則 \(i\) 不會影響 \(j\),正好與題目要求相反,所以倒著處理每個人。

轉移 \(1\) 的時間複雜度顯然是 \(O(n^2)\),對於轉移 \(2\),每向上一層數量減半,複雜度為 \(O(n\times(n+\frac{n}{2}+\frac{n}{4}+\dots))=O(n^2)\)

int n, m, mx, res, l[N], s[N], c[N << 1], f[N << 1][N << 1];
void Max (int &a, const int b) { if (b > a) a = b; }
void modify (int p, int x) {
    int tmp = 0; while (f[p][tmp] != INT_MIN) ++tmp;
    for (int i = tmp; i >= 1; --i) Max (f[p][i], f[p][i - 1] + c[p] - x);
}
void update (int p) {
    for (int i = 0; i <= n && f[p][i] != INT_MIN; ++i)
        Max (f[p + 1][i >> 1], f[p][i] + (i >> 1) * c[p + 1]);
    if (p < n + m) update (p + 1);
}
signed main() {
    read (n), read (m);
    for (int i = 1; i <= n; ++i) read (l[i]);
    for (int i = 1; i <= n; ++i) read (s[i]);
    for (int i = 1; i <= n + m; ++i)
        for (int j = 0; j <= n * 2; ++j) f[i][j] = INT_MIN;
    for (int i = 1; i <= n + m; ++i) read (c[i]), f[i][0] = 0;
    for (int i = n; i >= 1; --i) modify (l[i], s[i]), update (l[i]);
    for (int i = 1; i <= n + m + 1; ++i) Max (res, f[i][1]);
    return printf ("%lld\n", res), 0;
}

E

暴力做法如何確定最後的序列?列舉一個值 \(k\)\(b_i=(a_i<k)\),也就是隻記錄 \(a_i\)\(k\) 的大小關係。設 \(a',b'\) 為變換後最終得到的 \(a,b\)。取中位數的操作並不會改變這個大小關係。不難發現,一段長度 \(\ge2\) 的連續的 \(0/1\) 段永遠也不會變(稱它們為 \(0/1\) 段),相鄰的連續段之間夾了 \(010101\dots\) 這樣的東西,然後會從兩頭往中間擴散,這樣就得到了 \(b'\),算完所有 \(k\) 後也就可以得到 \(a'\),而操作次數就是所有 \(k\) 對應的 \(b\) 的最長的 \(01\) 段長度除以 \(2\)\(k\) 取所有 \(a_i\),複雜度 \(O(n^2)\)

介紹兩種優化方法。

第一種:依舊從小到大列舉 \(k=a_i\),此時 \(b\) 中有一些 \(0\) 變為 \(1\),對於每一個變化的位置,\(b’\) 中也有一些 \(0\) 變為 \(1\),且構成一個區間,可用 \(set\) 等資料結構維護,\(O(n \log n)\)。但又長又慢

第二種:對單個位置 \(x\) 進行計算,因為只考慮一個數,所以可以二分 \(k\),不必列舉。剩下的問題是如何找到離 \(x\) 最近的連續段。令 \(p_i=min/max(a_i,a_{i+1})\),然後反著處理 \(p\) 的最大值最小值的 \(ST\) 表(對於 \(min(a_i,a_{i+1})\) 反過來處理 \(max\)\(st\)\(MX\),對於 \(max(a_i,a_{i+1})\) 處理 \(min\)\(st\)\(MN\)),再套個二分分別算出最近的 \(0\) 段和 \(1\) 段,即可得到最近的連續段,\(O(n \log^2 n)\)。但還有更強的做法。不用二分 \(k\),直接二分距離 \(d\)。設 \(A=MN.ask(i-d-1,i+d),B=MX.ask(i-d-1,i+d)\)。取 \(k=min(A,B)\),此時 \(B\ge k\),說明在距離 \(d\) 內已經碰到 \(0\) 段,\(A\ge k\),說明距離 \(d\) 內沒有 \(1\) 段,那麼先碰到 \(0\) 段,\(b'_i=(a'_i<k)=0\),即 \(a’_i\ge k\)。只需找到最大的 \(k=min(A,B)\) 就是 \(a'_i\) 的值。隨著 \(d\) 增大,\(A\) 單減,\(B\) 單增,二分交點即可求得最大值(函式不連續,不一定有真正的交點)。至於操作次數,也可以順帶著求出

int n, res, a[N], lg[N], ma[N], na[N], b[N];
struct st {
    int c[20][N];
    void work (int *a) {
        memcpy (c[0], a, (n + 1) << 2);
        for (int j = 1; j < 20; ++j)
            for (int i = 0; i + (1 << j) - 1 <= n; ++i)
                c[j][i] = min (c[j - 1][i], c[j - 1][i + (1 << (j - 1))]);
    }
    int ask (int x, int d) {
        int l = x - d - 1, r = x + d, t = lg[r - l + 1];
        return min (c[t][l], c[t][r - (1 << t) + 1]);
    }
} mx, mn;
signed main() {
    read (n); lg[0] = -1;
    for (int i = 1; i <= n + 1; ++i) lg[i] = lg[i >> 1] + 1;
    for (int i = 1; i <= n; ++i) read (a[i]);
    a[0] = a[1], a[n + 1] = a[n];
    for (int i = 0; i <= n; ++i)
        ma[i] = -min (a[i], a[i + 1]), na[i] = max (a[i], a[i + 1]);
    mn.work (na), mx.work (ma);
    for (int i = 1; i <= n; ++i) {
        int l = 0, r = min (i - 1, n - i), mid;
        int ta, tb, val = 0;
        while (l <= r) {
            mid = l + r >> 1;
            ta = mn.ask (i, mid), tb = -mx.ask (i, mid);
            val = max (val, min (ta, tb));
            ta > tb ? l = mid + 1 : r = mid - 1;
        }
        b[i] = val, res = max (res, r + 1);
    }
    printf ("%d\n", res);
    for (int i = 1; i <= n; ++i) printf ("%d ", b[i]); puts ("");
    return 0;
}

F

。。。。。