題面
掛個 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
*/
總結
一般要離散化的題, 如果不好處理, 可以考慮:
對於不離散化的狀態, 需要哪些資訊?
然後將這些資訊離散化即可解決問題