CF1821D Black Cells 題解 貪心

題目鏈接: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章