社交距离 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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16049180.html