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

31 篇文章 2 订阅

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

具体的问题是这样的:

火星的鸡很特别,他们一只鸡脚会有 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;
}

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值