信号

信号

有 $n$ 个房子排成一排,从左到右依次编号为 $1 \sim n$。

其中一些房子内装有无线信号发射器。

这些信号发射器的有效覆盖半径为 $r$。

更准确地说,如果第 $p$ 号房子内装有信号发射器,则所有房间编号在 $\left[ {p−r+1,p+r−1} \right]$ 范围内的房子均可被其发出的无线信号覆盖,而其余房子则不会被其发出的无线信号覆盖。

例如,假设 $n=6,r=2$,且第 $2$、$5$ 号房子内装有信号发射器,则第 $2$ 号房子内的发射器发出的信号可以覆盖第 $1 \sim 3$ 号房子,第 $5$ 号房子内的发射器发出的信号可以覆盖第 $4 \sim 6$ 号房子,将两个发射器全部打开,则无线信号可以覆盖到所有房子。

初始时,所有无线信号发射器都是关闭的,请计算至少开启多少个无线信号发射器,才能保证所有房子都被无线信号覆盖到。

输入格式

第一行包含两个整数 $n$ 和 $r$。

第二行包含 $n$ 个整数 $a_{1},a_{2}, \dots,a_{n}$,每个数要么是 $1$,要么是 $0$,$a_{i}=1$ 表示第 $i$ 号房子内装有无线信号发射器,$a_{i}=0$ 表示第 $i$ 号房子内未装无线信号发射器。

输出格式

一个整数,表示需要开启的无线信号发射器的最少数量。

如果无法使得所有房子都被无线信号覆盖到,则输出 $−1$。

数据范围

前 $6$ 个测试点满足 $1 \leq n \leq 10$。
所有测试点满足 $1\leq n,r \leq 1000$,$0 \leq a_{i} \leq 1$。

输入样例1:

6 2
0 1 1 0 0 1

输出样例1:

3

输入样例2:

5 3
1 0 0 0 1

输出样例2:

2

输入样例3:

5 10
0 0 0 0 0

输出样例3:

-1

输入样例4:

10 3
0 0 1 1 0 1 0 0 0 1

输出样例4:

3

 

解题思路

  假设有两个信号发射器,一个比较靠前,一个比较靠后,并且两个信号发生器都可以覆盖到最左边的那个房子,即第$1$个房子。

  我们考虑一下应该选择哪个发生器。可以把所有的方案(最终答案的方案)分成两类,第一类是选择靠前的发生器的方案,第二类是选择靠后的发生器的方案。我们任取第一类中的方案,且这个方案可以覆盖所有的房子,我们把靠左的那个发生器换成靠右的发生器,仍然是一个合法的方案:

  而且需要的发生器的数量是相同的,所以对于第一类中的方案,我们都可以在第二类中找到一个方案,使得这个方案所需要的发生器不比第一类的方案多。所以第二类中的方案都不比第一类的方案差,因此第二类方案中必然存在最优解(第一类方案也可能存在最优解),所以我们只需要考虑第二类的方案就可以了,即应该选择靠后的那个发生器。

  以此类推,在能够覆盖到最左边那个还没被覆盖的房子的条件下,每次都应该选择尽可能靠后的房子。

  这题贪心的模型可以参考这个模板题:区间覆盖

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 1010;
 6 
 7 int a[N], sz;
 8 
 9 int main() {
10     int n, m;
11     scanf("%d %d", &n, &m);
12     for (int i = 1; i <= n; i++) {
13         int val;
14         scanf("%d", &val);
15         if (val) a[sz++] = i;   // 记录有发生器的房间
16     }
17     
18     int ret = 0, last = 0;  // last表示上一个被覆盖的房间,因此还没被覆盖的房间是last+1
19     for (int i = 0; i < sz; i++) {
20         if (last >= n) break;   // 所有的房间已被覆盖
21         // 第a[i]个房间最左边能够覆盖到的房间为a[i]-m+1,如果a[i]-m+1 > last+1,表明不能够覆盖到上一个未覆盖到的房间
22         if (a[i] - m > last) break;
23         
24         int j = i;  // 在能够覆盖last+1这个房间的前提下,寻找最靠右的房间
25         while (j + 1 < sz && a[j + 1] - m <= last) {
26             j++;
27         }
28         last = a[j] + m - 1;    // 更新last
29         i = j;
30         ret++;
31     }
32     
33     printf("%d", last >= n ? ret : -1);
34     
35     return 0;
36 }

  这题还可以用动态规划来做。

  AC代码如下,时间复杂度是$O(n \times m)$:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 1010;
 7 
 8 int a[N], f[N];
 9 
10 int main() {
11     int n, m;
12     scanf("%d %d", &n, &m);
13     for (int i = 1; i <= n; i++) {
14         scanf("%d", a + i);
15     }
16     
17     memset(f, 0x3f, sizeof(f));
18     for (int i = 1; i <= m; i++) {  // 初始化一开始能够覆盖到第1个房间的发射器
19         if (a[i]) f[i] = 1;
20     }
21     for (int i = 1; i <= n; i++) {
22         if (a[i]) {
23             for (int j = max(1, i - 2 * m + 1); j < i; j++) {   // 注意j可能会小于0越界
24                 if (a[j]) f[i] = min(f[i], f[j] + 1);
25             }
26         }
27     }
28     
29     int ret = N;
30     for (int i = max(1, n - m + 1); i <= n; i++) {  // 枚举能够覆盖到第n个房间的最后一个发射器
31         if (a[i]) ret = min(ret, f[i]);
32     }
33     printf("%d", ret == N ? -1 : ret);
34     
35     return 0;
36 }

  可以用单调队列优化到$O(n)$,AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1010;
 5 
 6 int a[N], f[N];
 7 int q[N], hh, tt = -1;
 8 
 9 int main() {
10     int n, m;
11     scanf("%d %d", &n, &m);
12     for (int i = 1; i <= n; i++) {
13         scanf("%d", a + i);
14     }
15     
16     memset(f, 0x3f, sizeof(f));
17     for (int i = 1; i <= m; i++) {
18         if (a[i]) f[i] = 1;
19     }
20     for (int i = 1; i <= n; i++) {
21         if (a[i]) {
22             while (hh <= tt && q[hh] + m - 1 < i - m) {
23                 hh++;
24             }
25             if (hh <= tt) f[i] = min(f[i], f[q[hh]] + 1);
26             while (hh <= tt && f[q[tt]] >= f[i]) {
27                 tt--;
28             }
29             q[++tt] = i;
30         }
31     }
32     
33     int ret = n + 1;
34     for (int i = max(1, n - m + 1); i <= n; i++) {
35         if (a[i]) ret = min(ret, f[i]);
36     }
37     printf("%d", ret == n + 1 ? -1 : ret);
38     
39     return 0;
40

 

参考资料

  AcWing 4421. 信号(AcWing杯 - 周赛):https://www.acwing.com/video/3871/

posted @ 2022-05-16 10:30  onlyblues  阅读(243)  评论(0编辑  收藏  举报
Web Analytics