社交距离 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$ 值。

 

解题思路

  分情况来讨论,一共有两种情况。

  1. 两头牛在同一个区间。
  2. 两头牛在不同的区间。

  我们把首尾区间和中间的区间分开来看。

  对于首尾区间,如果要把两头牛都放在首区间中,很明显,当把一头牛放在左端点时,另一头牛距离左右两头牛的距离才会尽可能的大。那么另一头牛应该放在什么地方?假设这头牛距离左端点的牛的距离为$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/

posted @ 2022-03-24 15:29  onlyblues  阅读(71)  评论(0编辑  收藏  举报
Web Analytics