1. 程式人生 > 其它 >3.14考試總結

3.14考試總結

別的不會,就會T1

\(n\) 個工人,第 \(i\) 個工人的初始效率值為 \(a_i\) 。 有 \(m\) 個工作,第 \(i\) 個工作需要恰好 \(k_i\) 個工人來完成。 你可以分配工人去完成工作,一個工人可以參加多個工作,但不能重複參加一個工作。當一個工人蔘加一個工作時,你會獲得等同於這個工人當前效率值的收益,然後這個工人效率值減 \(1\)。 你想知道你完成所有任務所能獲得的最大收益。

\(1\le n, m \le 5 \times 10^5\)
\(1\le k \le 10^6\)

考場上想了一堆亂搞做法,包括正解,但是思路一直有點歪

我們首先給所有的工作排個序,然後我們可以把它們的效率搞成一個柱狀圖

顯然的一個結論是我們每次要改的是前 \(k\) 大個

那麼我們會想,改完以後會怎麼樣

對於這樣一串數:\(6~6~5~5~5~4\)

如果我們令 \(k_i = 3\) 那麼我們的最終序列應該是 \(5~5~3~4~4~3\)
發現本來我們的數列是遞減的這麼一搞就不是了

但是我們繪成柱狀圖以後就會更好處理

這是剛開始的柱狀圖
那麼我們如果讓前面的每一段進行操作的話,肯定是橫著砍一刀,那麼就無法保證永遠不遞增

但是我們還有其他的辦法,對於這樣一串數,我們完全可以不用這種方式砍,我們考慮,對於前面的兩個6,你怎麼砍都不會有影響,但是對於這個五來說,就一定會有影響,我們考慮能不能找一個數代替5?答案是肯定的,因為我們發現,數列末尾的那個五減一是不影響答案的,因此我們就可以把這一段的第三個數減一改成第五個數減一,這樣就能繼續保證我們的數列單調遞減,每次我們只要搞到前面的 \(k_i\)

個就能統計對答案的貢獻,同時只要找到最後一段的後 \(k\) 個就能保證數列遞減

那麼問題就轉化得很顯然了,我們要找到最後一段的左端點和右端點,還得有區間減法

我們考慮用樹狀陣列或者線段樹來維護這些數,用線段樹先記下來各個和,然後每次我們先讓 \(ans+= [i-b[i]+1, n]\) 這一段的值,然後找到最後一個點 \(i-b[i]+1\) 的值,然後二分查詢這一段值的左右端點 \(l,r\),先修改和這一段無關的區間 \([r,n]\),對於後面一段本來應該是讓我們修改從 \([i-b[i] + 1,r]\),接下來我們只需要修改從左端點開始的 \([l,r-(i-b[i]+1) + l]\)

即可

但是我們發現寫兩個線段樹常數有億點點大,過不去,我們把第二個維護點值的線段樹改成樹狀陣列即可

inline int find(int val, int type) {
    int l = 1, r = n; res = 0;
    if (!type) {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (query(mid) >= val) res = mid, r = mid - 1;
            else l = mid + 1;
        }
    }
    else {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (query(mid) <= val) res = mid, l = mid + 1;
            else r = mid - 1;
        }
    }
    return res;
}

signed main(){
    // system("fc 1.out 2.out");
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
    freopen("2.out", "w", stdout);
#endif
    read(n);
    rep (i, 1, n) read(a[i]);
    read(m);
    rep (i, 1, m) read(b[i]);
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    rep (i, 1, n) add (a[i] - a[i - 1], i);
    build(1, 1, n);
    rep (i, 1, m) {
        int p = n - b[i] + 1;
        ans += query(1, p, n, 1, n);
        int val = query(p);
        int l = find(val, 0), r = find(val, 1);
        update(1, l, r - p + l, 1, n, -1);
        update(1, r + 1, n, 1, n, -1);
        add(-1, l), add(1, l + r - p + 1);
        add(-1, r + 1), add(1, n + 1);
    }
    write(ans, '\n');
    return 0;
}

核心就這一段,但是我調了好長時間,原因是線段樹寫掛了哈哈!我宣佈個事,我是個啥比