• XTU OJ 1464 黑子的鸡脚(说人话)


    最近泡椒鸡脚在火星特别流行,小黑家的订单供不应求,同时面对很多奇怪的订单,小黑不能及时的解决,于是他找到了他的儿子 “小黑” 来帮忙。

    具体的问题是这样的:

    火星的鸡很特别,他们一只鸡脚会有 n 根鸡指,并且是连续的、不成环的,你可以看成每一根鸡指的编号从 1 到 n。但是在饲养的过程中,总有鸡会因为一些意外而断掉一些鸡指。这就导致了鸡脚不完整。

    小黑家接到了很多订单,这些订单来自不同的地域,有着不同的习惯,他们喜欢吃只含有 k 根鸡指的泡椒鸡脚,并且鸡指是完整的(即不能有断指)。但是小黑家只有 n 指鸡,于是小黑子想到了一个办法,将一个鸡脚切开,伪装成 k 指鸡。

    但是由于有断指,并不能充分利用鸡脚。小黑的妹妹–“小黑”是学医的,她可以将鸡指接上(将断指复原),但是由于技术不精进,只能接上一根。小黑子想问你,对于一只给定的鸡脚,在最多接上了一根鸡指后,最多可以分割成多少只 k 指鸡脚。

    注意:分割成的每一个鸡脚,一定是原来的鸡脚中一段连续的区间

    一句话题意:

    给定一个长度为n的01串,你最多可以将一个0变成1,问最多可以提取出多少个不相交长度为k的全1子串

    输入描述#

    第一行一个整数 T,表示 T 组测试数据

    对于每组测试数据,描述如下:

    第一行为两个整数 n,k,表示小黑家的鸡有 n 根鸡指,k 表示订单的喜好是吃 k 指鸡脚。1≤n≤104,1≤k≤n

    第二行为一个长度为 n 的 01 字符串,按照下标编号 1 到 n ,如果第 i 个字符为 1,说明是好指;如果是 0,说明是断指。

    保证所有的测试数据中的所有 n 的和值不超过 3×106

    输出描述#

    输出 n 个整数。每一行一个,表示该组鸡脚最多复原一根后,能够分割成多少个 k 指鸡脚。

    样例#

    输入#

    2
    5 2
    10111
    8 3
    11101110
    

    输出#

    2
    2
    

    by xzl

    输入巨大,建议采用C语言风格的输入

    思路分析:考试的时候这道题把我卡住了,主要是太想暴力模拟了,总是想从双指针的角度去思考。

    我们只需要确定开头和结尾就可以确定一个区间了,因此直接用数组来储存就很方便,同时还可以储存有多少个区间。具体的分析见注释吧

    1. #include
    2. #include
    3. #define N 10007
    4. #define min(x, y) (x < y ? x : y)
    5. #define max(x, y) (x > y ? x : y)
    6. char s[N];
    7. int n, k;
    8. int L[N], R[N], cnt;
    9. void sol() {
    10. scanf("%d %d", &n, &k);
    11. int ans = 0;
    12. cnt = 0;//第多少个区间
    13. scanf("%s", s + 1);//这里有意思,因为如果直接输入%s的话,开头的下标为0,如果后面加1的话开头的下标就变为1了,这样就满足题目的编号条件
    14. s[0] = '0';//开头补0,不妨看题解,因为我们会尝试在一段区间开头或者结尾的0变成1来试试能不能将数量增加一个
    15. //因此为了保证不用特殊判断开头和中间的情况,我们直接开头补0,实际上不会影响我们的结果
    16. for (int i = 1; i <= n; ++i) {
    17. if (s[i - 1] == '0' && s[i] == '1') cnt ++, L[cnt] = N, R[cnt] = -1; //为什么这里取N和-1?,这是因为我们之后有一个min和max
    18. //为了保证更新的时候对两个端点也有影响,我们直接取端点的旁边的值,这样就不会对端点产生误判
    19. //上面的if同时保证了一个区间开头必定为1,开头前必定为0,为什么在开头前只看一个0?
    20. //这里其实有一种贪心的思想,比如,对于一个串 111000111111,你要获得数量最多的长度为k的连续为1的字串
    21. //是不是只能对最靠近1的0进行变化,才有可能使其能被提出来
    22. if (s[i] == '1') { // 更新块的信息
    23. L[cnt] = min(L[cnt], i);//储存一下区间坐标
    24. //左边肯定是取小的,右边肯定是取大的
    25. R[cnt] = max(R[cnt], i);
    26. }
    27. }
    28. for (int i = 1; i <= cnt; ++i) { // 对于每一块,取值。
    29. // printf("%d %d\n", L[i], R[i]);
    30. ans += (R[i] - L[i] + 1) / k;//如果说这个区间的长度为k倍数,那么数量就相应的变化;
    31. //我们其实不用判断能不能提出来,我们直接除以k,如果能提出来就!=0,不能提出来就=0
    32. //不需要单独判断了,我之前做的时候总想在这里判断一下
    33. //太过于麻烦
    34. }
    35. for (int i = 1; i <= cnt; ++i) { // 尝试合并
    36. //分为两种情况,情况一,如果说开头或者末尾为0,且后面没有连接的为1的连续字符串
    37. if ((R[i] - L[i] + 2) <= n && (R[i] - L[i] + 2) / k > (R[i] - L[i] + 1) / k) {
    38. //这段代码的意思是,如果说区间长度+1(也就是往后面或者前面延展一位:因为我们只能将一个0变为1,也就是说只能延展一位将其变为1)
    39. //的长度仍比长度n小(保证不会超出限制)与此同时延展一位后/k(代表分割成长度k的子串的数量)大于之前没有延展一位的子串的数量
    40. //代表将后一位或者
    41. //前一位的0变为1能够对最终答案的数量贡献1,因此就break,就不用再判断了
    42. //
    43. ans ++;
    44. break;
    45. }
    46. //情况2,末尾或者开头为0,但后面有连接的字符串
    47. if (i < cnt && R[i] + 2 == L[i + 1]) {
    48. int t1 = (R[i] - L[i] + 1) / k + (R[i + 1] - L[i + 1] + 1) / k;
    49. //将后面的字符串连接起来,看下能分出多少个长度为k的子区间
    50. int t2 = (R[i + 1] - L[i] + 1) / k;//如何保证两个子区间中间只相差一个0呢?
    51. //我们由之前的代码知道,两个区间的中间连接的必定为一个0,因此我们将这个0变为1
    52. //使得这个区间和下一个区间完全相连,来看一下有多少个长度为k的连续为1的子区间
    53. // printf("%d %d\n", t1, t2);
    54. if (t2 > t1) {
    55. ans ++;
    56. break;
    57. }
    58. }
    59. }
    60. if (cnt == 0 && k == 1)ans = 1;//特殊判断
    61. printf("%d\n", ans);
    62. }
    63. int main() {
    64. // freopen("std3.in", "r", stdin);
    65. // freopen("std3.out", "w", stdout);
    66. int T = 1;
    67. scanf("%d", &T);
    68. while (T--) sol();
    69. return 0;
    70. }

     

  • 相关阅读:
    【面试题】JSON.stringify()妙用,你真的知道吗?
    初阶数据结构 堆的问题详解 (三)
    js看代码说输出
    leetcode -658--找到 K 个最接近的元素
    C语言自学笔记9----用户自定义函数
    软件产品测试的准入准出标准有哪些?
    最强大脑记忆曲线(2)——创建数据库
    美食杰项目 -- 首页(一)
    C 语言 break和continue语句
    【汇编】指令系统的寻址方式
  • 原文地址:https://blog.csdn.net/qq_24917263/article/details/127943072