ZROI-21-CSP7連-DAY 7 T2

Yorg發表於2024-10-18

題面

掛個 pdf
題面下載

演算法

有點像掃描線?

容易想到離散化座標點, 那麼對於離散化之後的座標 \(x\), 粗略來看, 其能分開區間的個數即為 \(\displaystyle\sum_{i = 1}^{n} \left[{l_i < x < R_i}\right]\)
這個可以用類似於差分的方法解決, 每次對於一個區間 \(\left(l_i, r_i\right)\) , 將其區間整體加 \(1\)
發現到可以用差分, 即將差分陣列的 \(l_i + 1\) 位置加 \(1\), \(r_i\) 位置減 \(1\)
於是可以直接在離散化時按照 \(l_i + 1\)\(r_i\) 離散化

然後操作的時候需要注意: 離散化前後對應的區間長度改變
我使用 \(Back\) 陣列記錄離散化之前的真實座標

對於每一個離散化之後的點, 都可以計算出其被覆蓋的次數(差分陣列求字首和), 關鍵問題就在於怎麼去對應到原序列上
可以發現(以下均為離散化之後的點) \(i, i + 1, i \in [1, \rm{farthest \text{ } position})\) 這兩個點能夠組成一個區間, 對於區間中的數, 其覆蓋次數即為差分陣列字首和 \(i\) 的位置, 這個畫畫圖就能出來
也就是說, 對於離散化之後的每一個位置, 我們依次計算字首和相等的區間, 然後進行排序並處理

於是就可以寫程式碼了

程式碼

#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20;
const int MAXSIZE = 2e5 + 20;

int T;
int n, m;

struct node
{
    int Left, Right;
} Sec[MAXN];
int Pos[MAXSIZE];
int Pos_cnt = 0;
int Back[MAXSIZE];

void lsh()
{
    std::sort(Pos + 1, Pos + 2 * n + 1);
    Pos_cnt = std::unique(Pos + 1, Pos + 2 * n + 1) - (Pos + 1);

    for (int i = 1; i <= n; i++)
    {
        int Now_Left = std::lower_bound(Pos + 1, Pos + Pos_cnt + 1, Sec[i].Left) - Pos;
        Back[Now_Left] = Sec[i].Left;
        Sec[i].Left = Now_Left;

        int Now_Right = std::lower_bound(Pos + 1, Pos + Pos_cnt + 1, Sec[i].Right) - Pos;
        Back[Now_Right] = Sec[i].Right;
        Sec[i].Right = Now_Right;
    }
}

int Dif[MAXSIZE]; // 差分陣列

struct operation
{
    int Val; // 覆蓋次數
    int Len; // 對應原序列上的長度
} Cut[MAXSIZE];
bool cmp(operation a, operation b) { return a.Val > b.Val; }

int solve()
{
    /*離散化後, 對差分陣列進行計算*/
    memset(Dif, 0, sizeof(Dif));
    int MaxPos = 0, Ans = 0;
    for (int i = 1; i <= n; i++)
    {
        Dif[Sec[i].Right]--;
        Dif[Sec[i].Left]++;
        MaxPos = std::max(std::max(MaxPos, Sec[i].Right), Sec[i].Left);
    }

    /*覆蓋次數計算*/
    for (int i = 1; i <= MaxPos; i++)
    {
        Cut[i].Val = Cut[i - 1].Val + Dif[i];
        if(i != 1)
        {
            Cut[i].Len = Back[i] - Back[i - 1];
        }
    }
    std::sort(Cut + 1, Cut + MaxPos + 1, cmp);


    for (int i = 1; i <= MaxPos; i++)
    {
        int Cut_Num = std::min(m, Cut[i].Len);
        m -= Cut_Num;
        Ans += Cut_Num * Cut[i].Val;
    }

    return Ans;
}

signed main()
{

    scanf("%lld", &T);
    for (int Case = 1; Case <= T; Case++)
    {
        scanf("%lld %lld", &n, &m);
        Pos_cnt = 0;

        for (int i = 1; i <= n; i++)
        {
            /*這裡的 Sec 可以理解成對差分陣列操作的區間*/
            scanf("%lld %lld", &Sec[i].Left, &Sec[i].Right);
            Pos[++Pos_cnt] = ++Sec[i].Left;
            Pos[++Pos_cnt] = Sec[i].Right;
        }

        lsh();

        printf("case #%lld: %lld\n", Case, solve() + n);
    }

    return 0;
}

/*
2
3 1
1 3
2 4
1 4
3 3
1 3
2 4
1 4

Case #1: 5
Case #2: 7
*/

總結

一般要離散化的題, 如果不好處理, 可以考慮:
對於不離散化的狀態, 需要哪些資訊?
然後將這些資訊離散化即可解決問題