1. 程式人生 > 其它 >集合覆蓋模型例題_在打CodeForces的過程中發現的一個小模型

集合覆蓋模型例題_在打CodeForces的過程中發現的一個小模型

技術標籤:集合覆蓋模型例題

不久前的Grakn Forces 2020上,我想出了這個方法,我本來以為這個模型不會很常見。然而,今天的CodeForces #679 Div2上,我第二次碰到了可以用這個模型解決的問題,氣人的是,這次我卻在程式碼細節上寫掛了,最終掉分,憤而決定當作模板記錄下來,以免再犯。

問題

個有序對 ,對於每個有序對,可以選擇將 加入集合 或將 加入集合 ,試最小化 。(規定

演算法

首先注意到對於

,如果在 的同時有 ,那麼就可以捨棄 。於是可以將有序對以 為第一關鍵詞、
為第二關鍵詞排序,這樣可以保證 是隨 單調不減的:

顯然,

應該隨 單調減。如何保證呢?注意到最後一個有序對肯定要保留,所以我們只需要從後往前遍歷,劃掉破壞 單調性的元素即可:

當然,在程式設計中,刪除陣列中的元素還是太浪費時間了,我們轉而把無需刪除的有序對加入一個新的陣列。只不過,因為我們是從後往前遍歷,這樣會導致順序顛倒過來。

這時我們發現,如果我們選了某個

,那麼可以無代價地選擇它 下方的所有 ;如果我們選了某個 ,那麼可以無代價地選擇它 上方的所有 。相當於,我們只需要選擇左邊的 開頭和右邊的 結尾,而且它們應該是 相鄰
的:

40e62ba2afb16c0f013d278c07509aab.png

這樣一來,我們一共就只有

種選法了( 為新陣列的長度),把原來的指數級問題降成了線性。

程式碼

int minsum(vector<pair<int, int>> &V)
{
    if (V.empty())
        return 0;
    sort(V.begin(), V.end());
    vector<pair<int, int>> U{V[V.size() - 1]};
    for (int i = V.size() - 2; i >= 0; --i)
        if (V[i].second > U.back().second)
            U.push_back(V[i]);
    int mi = min(U[0].first, U.back().second);
    for (int i = 0; i < U.size() - 1; ++i)
        mi = min(mi, U[i].second + U[i + 1].first);
    return mi;
}

例題

CF1408D:通過計算,可求得每個強盜向上躲過探照燈需走

格,向右躲過探照燈需走 格,記為 。現在要讓所有強盜向上、向右各走若干格躲過所有探照燈,也即選若干個 求最大值,選剩餘的 求最大值,將這兩個最大值的和最小化。這完美符合剛剛那個模型 (當然,因為這模型就是從這個題來的-w-)

CF1435C:我們可以得到

個數,每組各選擇一個,讓選擇的數的極差最小。可以固定其中一組,對於這組數中的每一個 ,都把其餘各組中最大的 的數和最小的 的數找出來,分別求它們與 的差(如果沒有滿足某個條件的數,記對應的差為 INF)。這相當於是 向左擴充套件向右擴充套件。然後套模型。這樣我們算出了選固定組每一個數時的最小極差,求一個最小值即可。