把连续感染段用最大可行传播窗口覆盖,按边界段和内部偶数段限制窗口长度。
OJ: usaco
题目 ID: 1348
难度:普及-
标签:模拟区间覆盖贪心分类讨论usaco
日期: 2026-07-11 16:17
题意
有
每天晚上,已经感染的牛会把疾病传给左右相邻的牛;感染后会一直保持感染。
过了未知的若干天后,给出一个长度为 01 串,1 表示最终感染,0 表示最终没有感染。
要求:可能的最少初始感染牛数量是多少?
思路
先看一个枚举传播窗口长度的小数据暴力:
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 16:17
* update_at: 2026-07-11 16:18
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n;
string s;
int seg_len[MAXN];
bool seg_edge[MAXN];
int seg_cnt;
void collect_segments() {
seg_cnt = 0;
int i = 0;
while (i < n) {
if (s[i] == '0') {
i++;
continue;
}
int start = i;
while (i < n && s[i] == '1') {
i++;
}
int finish = i - 1;
seg_cnt++;
seg_len[seg_cnt] = finish - start + 1;
seg_edge[seg_cnt] = (start == 0 || finish == n - 1);
}
}
bool can_use_window(int window) {
for (int i = 1; i <= seg_cnt; i++) {
int len = seg_len[i];
int allowed;
if (seg_edge[i]) {
allowed = 2 * len - 1;
} else if (len % 2 == 0) {
allowed = len - 1;
} else {
allowed = len;
}
if (window > allowed) return false;
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
cin >> s;
collect_segments();
if (seg_cnt == 0) {
cout << 0 << '\n';
return 0;
}
int ans = n;
// 小数据暴力:枚举所有可能的传播窗口长度 2D+1。
for (int window = 1; window <= 2 * n + 1; window += 2) {
if (!can_use_window(window)) continue;
int now = 0;
for (int i = 1; i <= seg_cnt; i++) {
now += (seg_len[i] + window - 1) / window;
}
ans = min(ans, now);
}
cout << ans << '\n';
return 0;
}如果过了
的窗口。
于是最终的每一段连续 1,都需要用若干个这样的窗口覆盖。为了让初始感染牛数量最少,窗口长度应该尽量大。
现在问题变成:最终能使用的最大窗口长度是多少?
记一段连续 1 的长度为 len。
内部奇数段
如果这段 1 左右两边都有 0,并且 len 是奇数,那么最大窗口可以正好等于 len。
0 1 1 1 0
<--->
len = 3内部偶数段
窗口长度一定是奇数,也就是 0。
所以内部偶数段的最大窗口只能是 len - 1。
0 1 1 1 1 0
len = 4, 最大窗口 = 3边界段
如果一段 1 贴着最左端或最右端,传播可以从边界向内截断。长度为 len 的边界段,最大窗口可以达到:
例如最左边有三个 1:
1 1 1 0 ...可以理解为初始感染牛就在第一个位置,向左的传播被边界挡住,所以允许更大的天数。
因此,对每一段连续 1 计算它允许的最大窗口长度,取所有段的最小值作为全局窗口 window。
最后每段长度为 len 的连续 1 需要:
头初始感染牛。把所有段加起来就是答案。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 16:17
* update_at: 2026-07-11 16:18
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 300005;
int n;
string s;
int seg_len[MAXN];
bool seg_edge[MAXN];
int seg_cnt;
void collect_segments() {
seg_cnt = 0;
int i = 0;
while (i < n) {
if (s[i] == '0') {
i++;
continue;
}
int start = i;
while (i < n && s[i] == '1') {
i++;
}
int finish = i - 1;
seg_cnt++;
seg_len[seg_cnt] = finish - start + 1;
seg_edge[seg_cnt] = (start == 0 || finish == n - 1);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
cin >> s;
collect_segments();
if (seg_cnt == 0) {
cout << 0 << '\n';
return 0;
}
int window = 2 * n + 1;
for (int i = 1; i <= seg_cnt; i++) {
int len = seg_len[i];
int allowed;
if (seg_edge[i]) {
// 边界段可以由边界上的初始感染牛向内传播。
allowed = 2 * len - 1;
} else if (len % 2 == 0) {
// 内部偶数段不能由一个奇数长度窗口完全覆盖。
allowed = len - 1;
} else {
allowed = len;
}
window = min(window, allowed);
}
int ans = 0;
for (int i = 1; i <= seg_cnt; i++) {
ans += (seg_len[i] + window - 1) / window;
}
cout << ans << '\n';
return 0;
}复杂度
只需要扫描字符串收集连续 1 段,再扫描这些段统计答案。
时间复杂度为
总结
本题的核心是把“未知天数”转化成“未知窗口长度”。
窗口越大,需要的初始感染牛越少,所以先找所有连续 1 段共同允许的最大窗口,再用这个窗口覆盖每一段。