[NOI2019]序列 題解 (模擬費用流)

題意:

有兩個長度爲n的序列,要求從每個序列中選k個,並且滿足至少有l個位置都被選,問總和最大是多少。
\(1\leq l\leq k\leq n\leq 2*10^5\)
首先,記錄當前考慮到的位置i,第一個選的數量a, 第二個選的數量b,都被選的數量c,可以做到\(O(n^4)\)
卡常後能過\(n\leq 150\),有40分。
考慮正解:首先,看到這個範圍,可以認爲正解一定是貪心。
先看下\(n\leq 2000\),這個是網絡流的範圍。我們可以先建出費用流,然後再變爲模擬費用流,即貪心。
從源點向第一個序列連邊,第二個序列向匯點連邊。
然後,我們發現至少有l個位置都被選不太容易表示,因爲左面向對應的右面連的邊是分開的,無法放到一起考慮下限。
換一種思路:至少有l個位置都被選,就是剩的位置不超過\(k-l\)
所以,我們可以這樣建圖:
1、從源點向第一個序列連邊,第二個序列向匯點連邊。
2、對應位置連流量爲1的邊。
3、第一個序列都向點a連邊,點b向第二個序列連邊。a到b連容量\(k-l\)的邊。
求出流量爲k的最大費用流即可。這樣據說有64分。
考慮模擬費用流優化:
首先,記錄s表示a到b的剩餘流量。
1、若\(s>0\),則可以通過a到b的邊增廣,即從兩個序列中各選一個最大數,並把s減去1。

2、可以直接走左右的對應連邊增廣,即選一個左右相加最大的位置。
3、可以走S->x->x'->b->y'->T,走x'->b的前提是x在第二個序列中被選。
即在第一個序列中選一個第二個序列中選的位置,再選一個第二個序列中被選的作爲y。
4、與3對稱,即在第二個序列中選一個第一個序列中選的位置,再選一個第一個序列中被選的。
因爲是最長路增廣,所以在2,3,4中選最大的。以上操作可以用5個優先隊列完成。

然而,我們發現過不去樣例。
其實,算法是對的,但我們少了一些情況(特判):
首先,還可以走S->x->x'->b->a->y->y'->T。(情況5)
就是在第二個序列中選一個第一個序列中選的位置,再在第一個序列中選一個第二個序列中選的位置,
因爲退了a到b的流,要把s加1。

在1中,若選的兩個位置相同,則不用減s。若選的位置在另一序列中已被選,則爲情況3或4,不用減s。
若選的位置在另一序列中已被選,則爲情況5,把s加1。
在3,4中,可能會轉移到情況5,此時要把s加1。

加上這些後就能過了,實現時要注意細節。

代碼:

#include <stdio.h> 
#include <queue> 
using namespace std;
struct SJd {
    int z,  i;
    SJd() {}
    SJd(int Z, int I) {
        z = Z;i = I;
    }
};
bool operator < (const SJd a, const SJd b) {
    return a.z < b.z;
}
#define prio priority_queue < SJd > 
#define ll long long 
int sa[200010],sb[200010],ba[200010],bb[200010];
prio pa,pb,pc,pd,pe,em;
inline int read() {
    char ch;
    while ((ch = getchar()) < '0' || ch > '9');
    int rt = ch - '0';
    while ((ch = getchar()) >= '0' && ch <= '9') rt = (rt << 3) + (rt << 1) + ch - '0';
    return rt;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        pa = pb = pc = pd = pe = em;
        int n,k,l;
        n = read();
        k = read();
        l = read();
        for (int i = 0; i < n; i++) sa[i] = read();
        for (int i = 0; i < n; i++) sb[i] = read();
        for (int i = 0; i < n; i++) {
            pa.push(SJd(sa[i], i));
            pb.push(SJd(sb[i], i));
            pe.push(SJd(sa[i] + sb[i], i));
            ba[i] = bb[i] = false;
        }
        int sy = k - l;
        ll ans = 0;
        for (int i = 0; i < k; i++) {
            SJd ra,rb,rc,rd,re;
            while (!pa.empty()) {
                ra = pa.top();
                if (!ba[ra.i]) break;
                pa.pop();
            }
            while (!pb.empty()) {
                rb = pb.top();
                if (!bb[rb.i]) break;
                pb.pop();
            }
            if (sy > 0) {
                if (bb[ra.i]) sy += 1;
                if (ba[rb.i]) sy += 1;
                if (ra.i == rb.i) sy += 1;
                ba[ra.i] = bb[rb.i] = true;
                pc.push(SJd(sa[rb.i], rb.i));
                pd.push(SJd(sb[ra.i], ra.i));
                sy -= 1;
                ans += ra.z + rb.z;
                continue;
            }
            while (!pc.empty()) {
                rc = pc.top();
                if (!ba[rc.i]) break;
                pc.pop();
            }
            while (!pd.empty()) {
                rd = pd.top();
                if (!bb[rd.i]) break;
                pd.pop();
            }
            while (!pe.empty()) {
                re = pe.top();
                if (!ba[re.i] && !bb[re.i]) break;
                pe.pop();
            }
            int ma = -1,
            lx = -1;
            if (!pc.empty() && !pb.empty() && rc.z + rb.z > ma) ma = rc.z + rb.z,lx = 1;
            if (!pd.empty() && !pa.empty() && rd.z + ra.z > ma) ma = rd.z + ra.z,lx = 2;
            if (!pe.empty() && re.z > ma) ma = re.z,lx = 3;
            ans += ma;
            if (lx == 1) {
                if (ba[rb.i]) sy += 1;
                ba[rc.i] = bb[rb.i] = true;
                pc.pop();
                pb.pop();
                pc.push(SJd(sa[rb.i], rb.i));
            } else if (lx == 2) {
                if (bb[ra.i]) sy += 1;
                ba[ra.i] = bb[rd.i] = true;
                pa.pop();
                pd.pop();
                pd.push(SJd(sb[ra.i], ra.i));
            } else if (lx == 3) {
                ba[re.i] = bb[re.i] = true;
                pe.pop();
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章