最近泡椒鸡脚在火星特别流行,小黑家的订单供不应求,同时面对很多奇怪的订单,小黑不能及时的解决,于是他找到了他的儿子 “小黑子” 来帮忙。 具体的问题是这样的: 火星的鸡很特别,他们一只鸡脚会有 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
- #include
- #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;
- }