社交距離 I
社交距離 I
一種新型疾病,COWVID-19,開始在全世界的奶牛之間傳播。
Farmer John 正在採取儘可能多的預防措施來防止他的牛群被感染。
Farmer John 的牛棚是一個狹長的建築物,有一排共 $N$ 個牛欄。
有些牛欄裡目前有奶牛,有些目前空著。
得知“社交距離”的重要性,Farmer John 希望使得 $D$ 儘可能大,其中 $D$ 為最近的兩個有奶牛的牛欄的距離。
例如,如果牛欄 $3$ 和 $8$ 是最近的有奶牛的牛欄,那麼 $D=5$。
最近兩頭奶牛新來到 Farmer John 的牛群,他需要決定將她們分配到哪兩個之前空著的牛欄。
請求出他如何放置這兩頭新來的奶牛,使得 $D$ 仍然儘可能大。
Farmer John 不能移動任何已有的奶牛;他只想要給新來的奶牛分配牛欄。
輸入格式
輸入的第一行包含 $N$。
下一行包含一個長為 $N$ 的字串,由 $0$ 和 $1$ 組成,描述牛棚裡的牛欄。
$0$ 表示空著的牛欄,$1$ 表示有奶牛的牛欄。
字串中包含至少兩個 $0$,所以有足夠的空間安置兩頭新來的奶牛。
輸出格式
輸出 Farmer John 以最優方案在加入兩頭新來的奶牛後可以達到的最大 $D$ 值(最近的有奶牛的牛欄之間的距離)。
資料範圍
$2 \leq N \leq {10}^{5}$
輸入樣例:
14 10001001000010
輸出樣例:
2
樣例解釋
在這個例子中,Farmer John 可以以這樣的方式加入奶牛,使得牛欄分配變為 $10x010010x0010$,其中 $x$ 表示新來的奶牛。
此時 $D=2$。
不可能在加入奶牛之後取到更大的 $D$ 值。
解題思路
分情況來討論,一共有兩種情況。
- 兩頭牛在同一個區間。
- 兩頭牛在不同的區間。
我們把首尾區間和中間的區間分開來看。
對於首尾區間,如果要把兩頭牛都放在首區間中,很明顯,當把一頭牛放在左端點時,另一頭牛距離左右兩頭牛的距離才會儘可能的大。那麼另一頭牛應該放在什麼地方?假設這頭牛距離左端點的牛的距離為$d_{1}$,距離$x_{1}$的距離為$d_{2}$,那麼應該有$d_{1} + d_{2} = x_{1} - 1$。當$d_{1}$和$d_{2}$越接近時,$d_{1}$和$d_{2}$中的最小值才會越大,我們讓$d_{1} = d_{2} = \left\lfloor {\frac{x_{1}-1}{2}} \right\rfloor $。如果得到的距離是整數的話,那麼$d_{1}$和$d_{2}$取相等。如果不是整數,那麼下取整,比如$5$,那麼會得到一個是$2$,另一個是$3$,我們應該取$2$。
尾區間同理,最小的距離應該是$\left\lfloor {\frac{n-x_{m}}{2}} \right\rfloor$,$x_{m}$是序列中最後一頭牛的下標,在上面的序列中$m=5$。
對於中間的區間$\left[ {x_{i}, x_{i+1}} \right]$,當放入兩頭牛後會有三段距離,一段是一頭牛與區間左邊的牛的距離$d_{1}$,一段是兩頭牛之間的距離$d_{2}$,一段是另一頭牛與區間右邊的牛的距離$d_{3}$。同理會有$d_{1} + d_{2} + d_{3} = x_{i+1} - x_{i}$。要使這三個數的最小值最大,應該有讓$d_{1} = d_{2} = d_{3}$,假設$d_{1}$的值最小,那麼有$d_{1} = \left\lfloor {\frac{x_{i+1}-x_{i}}{3}} \right\rfloor$。
現在我們要看放在哪一個區間更好,也就是可以取到最大的最小值。
先看把牛放入中間區間的情況,假設在沒放牛之前的初始情況,任意兩個牛之間的距離為${x'}_{i}$,其中${x'}_{1} = x_{2} - x_{1}$,以此類推。假設我們把牛放入第$i$個區間,得到的最小距離是$y_{i} = \left\lfloor {\frac{x_{i+1}-x_{i}}{3}} \right\rfloor$,那麼最後所有牛間的最小值就應該是$min\left\{ {{x'}_{1}, {x'}_{2}, \dots, y_{i}, {x'}_{i+1}, \dots, {x'}_{m}} \right\}$,由因為${x'}_{i} > y_{i}$,因此我們可以把${x'}_{i}$放入進行比較,這並不會影響最終答案,因此就有$min\left\{ {{x'}_{1}, {x'}_{2}, \dots, {x'}_{m}}, y_{i} \right\}$。可以發現,這對於任何中間的區間都成立,只需要把$y_{i}$改成對應放入的區間即可。而對於首尾區間的情況,同樣只需要把$y_{i}$換成首尾區間的結果。因此為了使得最小的距離最大,應該取所有的$y_{i}$的最大值。假設一開始已存在的牛之間的最小距離為$x_{min} = min\left\{ {{x'}_{1}, {x'}_{2}, \dots, {x'}_{m}} \right\}$,因此對於把牛都放入同一個區間的情況,能夠取到的最大距離就為$min \left\{ {x_{min}, max \left\{ y_{i} \right\}} \right\}$。
然後是把牛放入不同區間的情況。如果放在首尾區間,那麼應該放在左端點或右端點上,距離分別為$x_{1} - 1$和$n - x_{m}$。如果是放在中間的中,那麼最小距離的最大值為$\left\lfloor \frac{x_{i+1} - x_{i}}{2} \right\rfloor$。我們在每一個區間中都放一頭牛得到上述的距離,最後在這些距離中找到最大值和次大值,那麼我們應該把這兩頭牛分別放入這兩個對應區間中,最小距離可以取到最大值。假設最大值和次大值分別為$y_{1}$和$y_{2}$,和第一種情況一樣$y_{i} < {x'}_{i}$,$y_{j} < {x'}_{j}$,因此能夠取到的最大距離就為$min \left\{ {x_{min}, y_{i}, y_{j}} \right\}$,即$min \left\{ {x_{min}, y_{j}} \right\}$。
最後的答案應該就是求出這兩種情況的結果中,取最小值。
如果不存在首區間或尾區間,根據上面的公式,會有$x_{1} - 1 = 0$及$n - x_{m} = 0$,對於兩種放法,我們都是取所有放入後的距離的最大值,而$0$並不會影響取最大值,因此也是成立的。
AC程式碼如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1e5 + 10; 6 7 char str[N]; 8 int pos[N]; 9 10 int main() { 11 int n; 12 scanf("%d %s", &n, str + 1); 13 14 int cnt = 0; 15 for (int i = 1; i <= n; i++) { 16 if (str[i] == '1') pos[++cnt] = i; // 記錄初始時有牛的位置 17 } 18 19 if (cnt == 0) { // 如果一頭牛的沒有,那麼兩頭牛應該放在左右端點 20 printf("%d", n - 1); 21 return 0; 22 } 23 24 int minx = N; // 初始時,兩頭牛之間的最小距離 25 for (int i = 1; i < cnt; i++) { 26 minx = min(minx, pos[i + 1] - pos[i]); 27 } 28 29 // 把兩頭牛放入同一個區間 30 int y = max(pos[1] - 1 >> 1, n - pos[cnt] >> 1); // 先對首尾區間這種特殊情況進行判斷 31 for (int i = 1; i < cnt; i++) { 32 y = max(y, (pos[i + 1] - pos[i]) / 3); // 處理中間區間的情況 33 } 34 35 // 把兩頭牛放入不同的區間 36 int y1 = pos[1] - 1, y2 = n - pos[cnt]; // 先對首尾區間進行特判 37 if (y1 < y2) swap(y1, y2); // y1是最大值,y2是次大值 38 for (int i = 1; i < cnt; i++) { 39 int t = pos[i + 1] - pos[i] >> 1; // 處理中間區間的情況 40 41 // 維護最大值和次大值 42 if (t >= y1) y2 = y1, y1 = t; 43 else if (t > y2) y2 = t; 44 } 45 46 printf("%d", min(minx, max(y, y2))); 47 48 return 0; 49 }
這題還可以用二分。一開始的搜尋的左右端點值為$left = 1, right = x_{min}$。
對於check函式,假設要判斷的最小距離的最大值能否為$x$,先從頭到尾掃描一遍序列,然後如果某個位置$i$為$0$,那麼把牛放入這個位置,然後判斷與前一頭牛和後一頭牛的距離,應該要滿足$i - pre \geq x ~ \&\& ~ i - ne_{i} \geq x$,其中$pre$表示前一頭牛的位置,$ne_{i}$為在$i$這個位置的後一頭牛的位置,可以預處理出來。只有滿足這個條件,才可以把牛放入,並更新pre為$i$。最後判斷能否放入兩頭牛。
AC程式碼如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int N = 1e5 + 10, INF = 0x3f3f3f3f; 7 8 int n; 9 char str[N]; 10 int a[N], ne[N]; 11 12 bool check(int x) { 13 int cnt = 0; 14 for (int i = 1, pre = -INF; i <= n; i++) { // 一開始pre設為負無窮,意味著在最左邊有一頭牛 15 if (a[i] == 0 && i - pre >= x && ne[i] - i >= x) { 16 if (++cnt >= 2) return true; 17 pre = i; 18 } 19 else if (a[i] == 1) { 20 pre = i; 21 } 22 } 23 24 return false; 25 } 26 27 int main() { 28 scanf("%d %s", &n, str + 1); 29 30 int left = 1, right = N; 31 for (int i = 1, pre = -INF; i <= n; i++) { 32 a[i] = str[i] - '0'; 33 if (a[i] == 1) { 34 right = min(right, i - pre); 35 pre = i; 36 } 37 } 38 39 // 預處理出來每頭牛的下一頭牛的位置,在正無窮處設一頭牛 40 memset(ne, 0x3f, sizeof(ne)); 41 for (int i = n; i; i--) { 42 if (a[i] == 0) { 43 if (a[i + 1] == 1) ne[i] = i + 1; 44 else ne[i] = ne[i + 1]; 45 } 46 } 47 48 while (left < right) { 49 int mid = left + right + 1 >> 1; 50 if (check(mid)) left = mid; 51 else right = mid - 1; 52 } 53 54 printf("%d", left); 55 56 return 0; 57 }
參考資料
AcWing 1659. 社交距離 I(春季每日一題2022):https://www.acwing.com/video/3747/
AcWing 1659. 社交距離 I 二分:https://www.acwing.com/solution/content/103535/