最近泡椒鸡脚在火星特别流行,小黑家的订单供不应求,同时面对很多奇怪的订单,小黑不能及时的解决,于是他找到了他的儿子 “小黑子” 来帮忙。 具体的问题是这样的: 火星的鸡很特别,他们一只鸡脚会有 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语言风格的输入 | ||
思路分析:考试的时候这道题把我卡住了,主要是太想暴力模拟了,总是想从双指针的角度去思考。 我们只需要确定开头和结尾就可以确定一个区间了,因此直接用数组来储存就很方便,同时还可以储存有多少个区间。具体的分析见注释吧 |
#include <stdio.h>
#include <string.h>
#define N 10007
#define min(x, y) (x < y ? x : y)
#define max(x, y) (x > y ? x : y)
char s[N];
int n, k;
int L[N], R[N], cnt;
void sol() {
scanf("%d %d", &n, &k);
int ans = 0;
cnt = 0;//第多少个区间
scanf("%s", s + 1);//这里有意思,因为如果直接输入%s的话,开头的下标为0,如果后面加1的话开头的下标就变为1了,这样就满足题目的编号条件
s[0] = '0';//开头补0,不妨看题解,因为我们会尝试在一段区间开头或者结尾的0变成1来试试能不能将数量增加一个
//因此为了保证不用特殊判断开头和中间的情况,我们直接开头补0,实际上不会影响我们的结果
for (int i = 1; i <= n; ++i) {
if (s[i - 1] == '0' && s[i] == '1') cnt ++, L[cnt] = N, R[cnt] = -1; //为什么这里取N和-1?,这是因为我们之后有一个min和max
//为了保证更新的时候对两个端点也有影响,我们直接取端点的旁边的值,这样就不会对端点产生误判
//上面的if同时保证了一个区间开头必定为1,开头前必定为0,为什么在开头前只看一个0?
//这里其实有一种贪心的思想,比如,对于一个串 111000111111,你要获得数量最多的长度为k的连续为1的字串
//是不是只能对最靠近1的0进行变化,才有可能使其能被提出来
if (s[i] == '1') { // 更新块的信息
L[cnt] = min(L[cnt], i);//储存一下区间坐标
//左边肯定是取小的,右边肯定是取大的
R[cnt] = max(R[cnt], i);
}
}
for (int i = 1; i <= cnt; ++i) { // 对于每一块,取值。
// printf("%d %d\n", L[i], R[i]);
ans += (R[i] - L[i] + 1) / k;//如果说这个区间的长度为k倍数,那么数量就相应的变化;
//我们其实不用判断能不能提出来,我们直接除以k,如果能提出来就!=0,不能提出来就=0
//不需要单独判断了,我之前做的时候总想在这里判断一下
//太过于麻烦
}
for (int i = 1; i <= cnt; ++i) { // 尝试合并
//分为两种情况,情况一,如果说开头或者末尾为0,且后面没有连接的为1的连续字符串
if ((R[i] - L[i] + 2) <= n && (R[i] - L[i] + 2) / k > (R[i] - L[i] + 1) / k) {
//这段代码的意思是,如果说区间长度+1(也就是往后面或者前面延展一位:因为我们只能将一个0变为1,也就是说只能延展一位将其变为1)
//的长度仍比长度n小(保证不会超出限制)与此同时延展一位后/k(代表分割成长度k的子串的数量)大于之前没有延展一位的子串的数量
//代表将后一位或者
//前一位的0变为1能够对最终答案的数量贡献1,因此就break,就不用再判断了
//
ans ++;
break;
}
//情况2,末尾或者开头为0,但后面有连接的字符串
if (i < cnt && R[i] + 2 == L[i + 1]) {
int t1 = (R[i] - L[i] + 1) / k + (R[i + 1] - L[i + 1] + 1) / k;
//将后面的字符串连接起来,看下能分出多少个长度为k的子区间
int t2 = (R[i + 1] - L[i] + 1) / k;//如何保证两个子区间中间只相差一个0呢?
//我们由之前的代码知道,两个区间的中间连接的必定为一个0,因此我们将这个0变为1
//使得这个区间和下一个区间完全相连,来看一下有多少个长度为k的连续为1的子区间
// printf("%d %d\n", t1, t2);
if (t2 > t1) {
ans ++;
break;
}
}
}
if (cnt == 0 && k == 1)ans = 1;//特殊判断
printf("%d\n", ans);
}
int main() {
// freopen("std3.in", "r", stdin);
// freopen("std3.out", "w", stdout);
int T = 1;
scanf("%d", &T);
while (T--) sol();
return 0;
}