洛谷題單指南-字首和差分與離散化-P1496 火燒赤壁

五月江城發表於2024-07-28

原題連結:https://www.luogu.com.cn/problem/P1496

題意解讀:給定n個區間[a,b),計算所有區間覆蓋的總長度。

解題思路:

方法1、離散化

先思考一種比較直觀的思路:

既然要計算多個區間覆蓋的總長度,可以列舉每一個區間[a,b),透過一個桶陣列來標記區間中所有的點f[x] = 1,最終統計所有為1的點數量,即可得到總長度。

但是,要注意此題區間[a,b)中a,b的取值範圍−2^31≤𝑎<𝑏<2^31,無法透過桶陣列來標記,會導致記憶體超限。

怎麼解決呢?這裡就要藉助離散化的方法!

所謂離散化,就是用數字的相對值替代他們的絕對值,能夠將分佈廣而稀疏的數字轉為連續的、密集的分佈,從而起到節省空間的效果。

對於本問題,儘管a,b取值範圍較大,但是區間不超過20000個,也就是a,b的值最多40000個,我們可以建立一個1~40000的數值與真實a、b之間的對映關係,如樣例:建立一個c[]陣列,編號與a,b數值對應關係如下

c[] 1 2 3 4 5 6
a,b數值 -1 1 2 5 9 11

我們透過桶陣列標記所有[-1,1),[5,11),[2,9)的值對應的編號為1

[-1,1):對應編號[1,2),f[1] = 1

[5,11):對應編號[4,6),f[4] = f[5] = 1

[2,9):對應編號[3,5),f[3] = f[4] = 1

綜上:

f[] 1 2 3 4 5 6
1 0 1 1 1 0

再透過列舉f[]陣列計算覆蓋的總長度,對於每一個f[i] = 1,其所表示在真實區間上的數值覆蓋長度是開始c[i+1]-c[i]

那麼,如何計算c[]陣列,也就是如何建立離散化之後的有序數字與原始a,b數值之間的對映關係?

可以如下處理:

第一步、將a[]、b[]所有的數字儲存到新的陣列c[]

第二步、對c[]進行排序、去重

第三步、對a[]、b[]中每一個數字,透過二分在c[]中查詢第一齣現的下標,並用下標更新a[],b[]中原來的數字

即對a[],b[]中每一個數字完成了離散化處理。

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 20005;

int n;
int a[N], b[N], c[2 * N], cnt;
int f[2 * N];
long long ans;

int bs(int x)
{
    int l = 1, r = cnt, res = -1;
    while(l <= r)
    {
        int mid = l + r >> 1;
        if(c[mid] >= x) res = mid, r = mid - 1;
        else l = mid + 1;
    }
    return res;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i];
        c[++cnt] = a[i];
        c[++cnt] = b[i];
    }

    //排序
    sort(c + 1, c + cnt + 1);
    //去重
    int j = 0;
    for(int i = 1; i <= cnt; i++)
        if(i == 1 || c[i] != c[i - 1]) 
            c[++j] = c[i];
    cnt = j;
    //將a,b離散化
    for(int i = 1; i <= n; i++)
    {
        int x = bs(a[i]);
        int y = bs(b[i]);
        //標記著火的點
        for(int j = x; j < y; j++)
            f[j] = 1;
    }
    //列舉著火點,計算對應真實位置的長度
    for(int i = 1; i <= cnt; i++)
    {
        if(f[i])
        {
            ans += c[i + 1] - c[i];
        }
    }
    cout << ans;

    return 0;
}

方法2、區間合併

參考:https://www.cnblogs.com/jcwy/p/18208437 裡介紹的區間合併方法。

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 20005;

struct node
{
    int a, b;
    bool operator < (const node &x) const
    {
        return a < x.a;
    }
};
node range[N];
vector<node> newrange;
int n, ans;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> range[i].a >> range[i].b;
    }
    //將區間按左端點從小到大排序
    sort(range + 1, range + n + 1);
    
    //以下進行區間合併
    node cur = range[1]; //初始區間為第一個區間
    for(int i = 2; i <= n; i++)
    {
        if(range[i].a > cur.b)
        {
            newrange.push_back(cur);
            cur = range[i];
        }
        else
        {
            cur.b = max(cur.b, range[i].b);
        }
    }
    newrange.push_back(cur);

    //計算區間覆蓋長度
    for(auto i : newrange)
    {
        ans += i.b - i.a;
    }
    cout << ans;

    return 0;
}

相關文章