原題連結: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;
}