題目鏈接:https://codeforces.com/problemset/problem/1821/D
題目大意
在一條數軸上有無窮個點,下標爲 \(0, 1, 2, \ldots\),初始時每個點都是白色的。
你控制着一個機器人,初始時機器人位於座標爲 \(0\) 的那個點。
機器人有兩種狀態:激活狀態 和 非激活狀態。
當處於激活狀態時,機器人所在的點都會被染成黑色。
處於非激活狀態時,機器人所在的點不會被染成黑色。
初始時機器人處於非激活狀態。
你可以對這個機器人進行若干次操作,操作分爲三種類型。每一次操作,你可以選擇如下三種操作之一執行:
- 將機器人的位置像數軸的正方向移動一個單位(即:若當前機器人在座標 \(x\),則執行一次該操作後機器人將移動到座標 \(x+1\) 的那個點);
- 激活機器人:該操作只有當機器人處於非激活狀態時才能執行,執行該操作後機器人將變爲 激活狀態;
- 撤銷激活機器人:該操作只有當機器人處於激活狀態時才能執行,執行該操作後機器人將變爲 非激活狀態。
有 \(n\) 個區間,第 \(i\) 個區間用 \([l_i, r_i]\) 表示。
你需要使用最少的操作次數,將至少 \(k\) 個點染成黑色,但是有一個限制,就是:這些染成黑色的點必須包含在至少一個給定的區間中,這也就是說,如果你要將座標爲 \(x\) 的那個點染成黑色,則必須保證存在一個 \(i(1 \le i \le n)\) 滿足 \(l_i \le x \le r_i\)。
同時,本題也要求操作結束時機器人恢復到非激活狀態(這也就意味着最少操作次數對應的最後一次操作是 撤銷激活機器人)。
問:至少需要進行幾次操作能夠使至少 \(k\) 個點被染成黑色,且最終機器人處於非激活狀態?
解題思路
首先是考慮從前往後枚舉第 \(i\) 個線段。
考慮只對前 \(i\) 個線段裏面的點進行染色。那麼會出現兩種情況:
- 情況1:前 \(i\) 個線段裏面可以被染色的點的個數 \(\lt k\) 個 —— 此時因爲點的數量不夠,所以不做討論;
- 情況2:前 \(i\) 個線段裏面可以被染色的點的個數 \(\ge k\) 個,此時需要重點分析。
當前 \(i\) 個線段裏面可以被染色的點的個數 \(\ge k\) 個時,我們還需要考慮 —— 最優解有沒有給第 \(i\) 個線段中的點染色?
如果最優解沒有給第 \(i\) 個線段中的點染色,則該情況可以直接轉移到前 \(i-1\) 個線段的情況(因爲我們時枚舉前 \(i\) 個線段的,枚舉前 \(i\) 個線段之前肯定已經枚舉了前 \(i-1\) 個線段,所以這種情況實際上已經分析過了)。
所以此時的情況肯定是給第 \(i\) 個線段中的點染色的。
首先,對於前面的所有線段,只要是選擇染色的,將前面這些線段中的點全部都染色肯定不會差,因爲:
對於一個線段,只要有一個點選擇染色了,那麼肯定具有 激活(原題中的 shift)和 撤銷激活(原題中的 release shift)操作了,此時再對這個線段中的其它點進行染色的額外操作此時是確定的,多染一個點只需要多操作一次。
所以,對於前 \(i-1\) 個線段:
- 要麼選擇全都不染(這個線段棄用掉);
- 要麼選擇全都染(然後第 \(i\) 個線段把剩餘的個數補齊)。
所以此時需要考慮的是前 \(i-1\) 個線段中哪些需要棄用掉。
我一開始有一種錯誤的貪心思想,就是在保證前 \(i\) 個線段中保留的線段對應的點的個數 \(\ge k\) 的情況下,儘量多刪除前面的長度小的線段,但是發現這種錯誤思想是錯誤的。舉個反例:
1
2 3
1 3
2 5
此時:當 \(i = 2\) 時,棄用掉第一段的最少操作次數爲 \(7\);不棄用第 \(1\) 段的操作此時是 \(6\)。
只有長度爲 \(1\) 的線段纔有可能被棄用。
爲什麼呢,因爲在考慮前 \(i\) 個線段並且第 \(i\) 個線段是需要激活的情況下,是肯定需要移動到 \(l_i\) 的,而 \(r_{i-1} \lt l_i\),對於一個長度 \(\ge 2\) 的線段(假設爲第 \(j\) 個線段)\([l_j, r_j]\),這些點段中的點都是要移動到的,當線段中的點數 \(\ge 2\) 時,只需要兩步額外的操作(激活/shift 和 撤銷激活release shift)就能夠使這個線段中的所有點都染色(這不是很香嘛),至少不會比在第 \(i\) 個線段中向右移動一格要花更多的操作次數。
而對於長度爲 \(1\) 的線段,移動也是需要移動的,所以不考慮移動的開銷。但是如果給這一個點染色需要額外操作兩次(激活/shift 和 撤銷激活release shift)。而後面的第 \(i\) 個線段只需要多移動依次就能多一個點染色。所以對於長度爲 \(1\) 的點,棄用它們更優。
所以,只有長度爲 \(1\) 的線段纔有可能被棄用。
但是棄用歸棄用,對於每個枚舉到的 \(i\),當前 \(i\) 個線段的對應的所有可以染色的點的數量已經 \(\ge k\) 時,不能啓用到剩餘點的個數 \(\lt k\) —— \(\lt k\) 了就不是一個可發的答案了。
綜上所述,就是枚舉前 \(i\) 個線段,然後把前面長度爲 \(1\) 的線段能棄用的都棄用了。那麼此時,假設:
- 沒有被棄用的線段對應的點的數量爲 \(sum\)
- 沒有被棄用的線段的數量是 \(cnt\) 個
則:考慮到從座標 \(0\) 移動到 \(r_i\),並且沒有棄用的線段都染色了,那麼:
- 移動帶來的花費是 \(r_i\),
- 每個線段激活和撤銷激活的花費是 \(cnt \times 2\)
所以給前 \(i\) 個線段中沒有棄用的線段中的點都染色的花費是 \(r_i + cnt \times 2\)。
但是,因爲我只需要給 \(k\) 個點染色就可以了,但是我卻選擇給了 \(sum\) 個點染色(\(sum \ge k\)),那麼在最後一個線段中,我其實可以減少 \(sum - k\) 個點的染色(減小 \(sum - k\) 次向右的移動),此時答案的一個候選項是:
\(r_i + cnt \times 2 - (sum - k)\)
而我的答案就是所有合法的 \(r_i + cnt \times 2 - (sum - k)\) 的最小值。
示例程序
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int T, n, k, l[maxn], r[maxn];
int cal() {
int ans = -1, cnt = 0, sum = 0, cnt1 = 0;
for (int i = 1; i <= n; i++) {
sum += r[i] - l[i] + 1;
cnt++;
cnt1 += (l[i] == r[i]);
if (sum >= k) {
int x = min(sum-k, cnt1);
sum -= x;
cnt1 -= x;
cnt -= x;
int tmp = r[i] + cnt * 2 - (sum - k);
if (ans == -1 || ans > tmp)
ans = tmp;
}
}
return ans;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", l+i);
for (int i = 1; i <= n; i++) scanf("%d", r+i);
printf("%d\n", cal());
}
return 0;
}