USACO历年青铜组真题解析 | 2023年12月Cowntact Tracing 2

2023-12-26 21:24:17

学习C++从娃娃抓起!记录下USACO(美国信息学奥赛)备考青铜组别比赛学习过程中的题目,记录每一个瞬间。

附上汇总贴:USACO历年青铜组真题解析 | 汇总-CSDN博客


【题目描述】

农夫约翰(FJ)的N头(1≤N≤3?10^5)奶牛排成一行。不幸的是,疾病正在这些奶牛中传播。

有些奶牛初始就已经被感染了。每天晚上,都会有一头被感染的奶牛把疾病传播给它左右两边的奶牛(如果它们存在的话)。一旦一头奶牛被感染,她就会持续处于感染状态。

在经过了一段时间后,农夫约翰意识到问题已经无法控制了, 所以他对他的奶牛进行检测来确定哪些奶牛感染了这种疾病。找出最初感染这种疾病的奶牛数量的最小值。

【输入】

第一行包含一个整数N,表示农夫约翰拥有的奶牛数量。

第二行包含一个长度为N的字符串,这个字符串只包含1和0,用来表示若干个晚上后每头奶牛的感染状态,其中1表示被感染了,0表示没有被感染。

【输出】

输出一个整数:最初感染这种疾病的奶牛数量的最小值。

【输入样例】

5
11111

【输出样例】

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

文章来源:https://blog.csdn.net/guolianggsta/article/details/135209973
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。