题解:洛谷 P9975 Cowntact Tracing 2
【题目来源】
洛谷:P9975 [USACO23DEC] Cowntact Tracing 2 B - 洛谷
【题目描述】
Farmer John 有 \(N\) 头奶牛排成一列(\(1 \leq N \leq 3\cdot 10^5\))。不幸的是,有一种疾病正在传播。
最初,有一些奶牛被感染。每到夜晚,被感染的奶牛会将疾病传播给它左右两边的奶牛(如果这些奶牛存在的话)。一旦奶牛被感染,她就会持续处于感染状态。
经过一些晚上,Farmer John 意识到情况已经失控,因此他对奶牛进行了检测以确定哪些奶牛感染了疾病。请找出最少有多少头奶牛最初可能感染了这种疾病。
【输入】
第一行为一个整数 \(N\),即 Farmer John 拥有的奶牛数量。
接下来一行,包含长度为 \(N\) 的由 \(1\) 和 \(0\) 组成的位串。其中 \(1\) 表示一头被感染的奶牛,\(0\) 表示一头在经过若干晚之后仍未被感染的奶牛。
【输出】
输出一个整数,表示最少有多少头奶牛可能最初感染了这种疾病。
【输入样例】
5
11111
【输出样例】
1
【核心思想】
-
问题分析:给定一个长度为 \(N\) 的 0/1 字符串,1 表示最终被感染的奶牛,0 表示始终未被感染。传播规则:每晚,已感染的奶牛会将其左右相邻的健康奶牛感染。求最少可能的初始感染奶牛数量。核心在于:连续 1 段的长度决定了该段所需的最少初始感染数,而连续 0 段的长度(包括两端)限制了传播的“时间窗口”——即最多能传播多少晚,从而决定了每个 1 段内部可以合并的初始感染点数量。
-
算法选择:
- 贪心 + 分段处理:将字符串分为左端连续 1 段、中间若干由 0 分隔的 1 段、右端连续 1 段。每段所需最小初始感染数由段长和全局“传播能力”(即最大的连续 0 段长度决定的最多传播天数)共同决定。
- 关键变量:
minn表示所有连续 1 段对应的最大传播天数的最小值。传播天数不能超过任何一段的限制,因此取所有限制的最小值作为全局可用传播天数。
-
关键步骤(基于代码逻辑):
- 特判全 1 或全 0:全 1 只需 1 头初始感染;全 0 则为 0。
- 特判模式:如
"010"、"10"、"01"、"0110"等,这些情况初始感染数等于当前 1 的个数(传播受限,无法合并)。 - 计算左端连续 1 长度
left1和右端连续 1 长度right1。 - 计算中间连续 1 段的长度数组
a[group](被 0 分隔)。 - 确定最小传播天数
minn:- 左端
left1能传播的最大天数为left1 - 1。 - 右端同理。
- 中间长度为
len的 1 段,最大传播天数为 \(\lfloor (len-1)/2 \rfloor\)(奇数(len-1)/2,偶数(len-2)/2)。 minn取上述所有值的最小值。
- 左端
- 计算总初始感染数
sum:- 对每个中间 1 段,所需初始感染数为 \(\lceil len / (2 \cdot minn + 1) \rceil\)。
- 对左端 1 段,若
left1 <= minn则只需 1 头;否则需要 \(\lceil (left1 - minn) / (2 \cdot minn + 1) \rceil + 1\) 等。 - 右端同理。
- 输出
sum。
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),线性扫描。
- 空间复杂度:\(O(N)\),存储数组和字符串。
-
贪心 + 分段覆盖的核心思想:
- 传播等价于区间扩张:一个初始感染点经过 \(t\) 晚后,会覆盖其左右各 \(t\) 个位置,形成一个长度为 \(2t+1\) 的连续 1 段。因此,给定一个最终 1 段,求最少初始点等价于用若干长度为 \(2t+1\) 的区间覆盖它。
- 传播天数受限:\(t\) 不能超过该 1 段两侧 0 段的长度(因为传播到 0 段边界就会停止,无法跨越)。因此,全局可用的最大传播天数 \(t\) 是所有 1 段对应限制的最小值。
- 贪心覆盖:确定 \(t\) 后,每个 1 段需要的最少初始点数就是 \(\lceil len / (2t+1) \rceil\)(中间段),边界段由于只需向一侧传播,覆盖长度为 \(t+1\),故公式略有不同。
- 适用场景:适用于一维传播模型,已知最终感染状态,求最小初始源点数量。核心是识别连续段并利用传播对称性。
【解题思路】

【算法标签】
普及 #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, minl=1e9, maxl=-1e9, minr=1e9, maxr=-1e9, minn=1e9, minm=1e9, left1=0, right1=0, sum=0;
int cnt0=0, cnt1=0;
int a[300005];
string s;
int main()
{
cin >> n >> s; // 输入n和0、1字符串
s = " " + s + " "; // 字符串的2端增加空格
for (int i=1; i<=n; i++) { // 统计1的个数
if (s[i]=='1') cnt1++;
else cnt0++;
}
if (cnt1==n) { // 如果全为1,则输出1
cout << 1 << endl;
exit(0);
}
if (cnt0==n) { // 如果全为0,则输出0
cout << 0 << endl;
exit(0);
}
if ((s.find("010")>=0 && s.find("010")<=n) || (s.find(" 10")>=0 && s.find(" 10")<=n) || (s.find("01 ")>=0 && s.find("01 ")<=n) || (s.find("0110")>=0 && s.find("0110")<=n)) { // 进行特判,该些场景下最初1的个数就是现在1的个数
cout << cnt1 << endl;
exit(0);
}
int st=1, ed=n; // 定义起始和结束位置
if (s[1]=='1') { // 从起始位置开始,找左端连续1的个数
for (int i=1; i<=n; i++) {
if (s[i]=='0') {
st = i;
break;
}
left1++;
}
}
if (left1>0) { // 如果有的话,left1可调整的最小值为left1-1(其实就是最大值)
minn = min(minn, left1-1);
}
if (s[n]=='1') { // 从结束位置开始,找右端连续1的个数
for (int i=n; i>=1; i--) {
if (s[i]=='0') {
ed = i;
break;
}
right1++;
}
}
if (right1>0) { // 如果有的话,right1可调整的最小值为right1-1(其实就是最大值)
minn = min(minn, right1-1);
}
int tmp = 0, group=0; // 定义中间连续1个个数tmp和有多少组group
char last = s[st]; // 此时st位置为从左边的第一个0
for (int i=st+1; i<=ed; i++) { // 从左边的第一个0开始
if (s[i]=='1') { // 如果是1
tmp++; // 个数自增1
} else if (last=='1' && s[i]!='1'){ // 如果遇到1之后的第一个0
a[group] = tmp; // 记录该连续1的个数,并存到数组中
tmp=0; // 重置tmp为0
group++; // 组数自增1
}
last = s[i]; // 更新last为当前字符
}
for (int i=0; i<group; i++) { // 统计中间连续1中最小调整次数
if (a[i]%2==1) { // 分奇偶讨论
minn = min(minn, (a[i]-1)/2);
} else {
minn = min(minn, (a[i]-2)/2);
}
}
for (int i=0; i<group; i++) { // 得到最小调整次数后,计算中间每段连续1的初始1的个数,并累积求和
sum += (a[i]+(2*minn+1-1))/(2*minn+1);
}
if (left1>0) { // 计算左端连续1的初始1个数
int tmp = (left1+2*minn)/(2*minn+1);
if (tmp>=left1) { // 当根据公式计算的值大于等于left1个数时
sum += left1-minn; // 就加上left1-minn,即开始最左边为1
} else { // 否则像计算中间一样计算并累加
sum += tmp;
}
}
if (right1>0) { // 计算右端连续1的初始1个数
int tmp = (right1+2*minn)/(2*minn+1);
if (tmp>=right1) { // 当根据公式计算的值大于等于right1个数时
sum += right1-minn; // 就加上right1-minn,即开始最右边为1
} else { // 否则像计算中间一样计算并累加
sum += tmp;
}
}
cout << sum << endl; // 输出结果
return 0;
}
【运行结果】
5
11111
1
浙公网安备 33010602011771号