CF1398F

本文详细介绍了如何使用动态规划和区间线段树解决CF1398F问题,提出两种方法,一种时间复杂度为O(nlnnlogn),另一种优化后达到O(nlogn)。通过预处理最长不含01后缀,实现快速查询和跳转,降低了复杂度并提高了效率。更新后的代码在全1数据下表现更优。

CF1398F

Solution

我又来贡献暴力做法了。。。听说两只log不可能过1e6?

有一个显然的想法是:

我们先预处理一个aia_iai表示第iii个位置的最长后缀长度,满足该后缀中不同时存在0和1。

假设我们要求x=ix=ix=i的答案,那么一个位置jjj可以作为一轮的结束点当且仅当aj≥ia_j\geq iaji,而一个结束位置序列p1<p2<...<pkp_1<p_2<...<p_kp1<p2<...<pk合法当且仅当每一个pjp_jpj都是合法结束点并且相邻两个元素的差至少为iii(保证不重合)。

因此我们会贪心地从第一个满足aj≥ia_j\geq iajijjj开始,每次找一个最小的j′j'j,满足j′≥j+ij'\geq j+ijj+iaj′≥ia_{j'}\geq iaji,然后从jjj跳到j′j'j,最终的答案就是跳的步数。

这显然可以直接对aia_iai建区间线段树,维护区间内的maxmaxmax,线段树上二分求出j′j'j

这个方法的时间复杂度为O(∑ansi  log⁡n)O(\sum ans_i\;\log n)O(ansilogn),而ansi≤⌊ni⌋ans_i\leq \lfloor\frac{n}{i}\rflooransiin,因此总时间复杂度O(nln⁡nlog⁡n)O(n\ln n\log n)O(nlnnlogn),注意常数即可。

(PS:别用偷懒用set维护,常数起飞,实测接近线段树的三倍)。

Code

char st[MAXN];
int mx[MAXN << 2], a[MAXN];
void build(int x, int l, int r) {
	if (l == r) { mx[x] = a[l]; return; }
	int mid = (l + r) >> 1;
	build(x << 1, l, mid);
	build(x << 1 | 1, mid + 1, r);
	mx[x] = max(mx[x << 1], mx[x << 1 | 1]);
}
int query(int x, int l, int r, int L, int y) {
	if (mx[x] < y) return -1;
	if (l == r) return l;
	int mid = (l + r) >> 1;
	if (L > mid) return query(x << 1 | 1, mid + 1, r, L, y);
	else {
		int t = query(x << 1, l, mid, L, y);
		if (t != -1) return t;
		return query(x << 1 | 1, mid + 1, r, mid + 1, y);
	}
}
signed main() {
#ifndef ONLINE_JUDGE
	freopen("a.in", "r", stdin);
#endif
	int n, cnt0 = 0, cnt1 = 0;
	read(n), reads(st);
	for (int i = 1, l = 1; i <= n ; ++ i) {
		auto add = [&](char x, int y) {
			if (x == '0') cnt0 += y; 
			if (x == '1') cnt1 += y;
		};
		add(st[i], 1);
		while (cnt0 && cnt1) add(st[l ++], -1);
		a[i] = i - l + 1;
	}
	build(1, 1, n);
	for (int i = 1; i <= n ; ++ i) {
		int cnt = 0, r = 1;
		for (; r <= n ;) {
			r = query(1, 1, n, r, i);
			if (r == -1) break;
			r += i;
			++ cnt;
		}
		print(cnt), putc(' ');
	}
	return 0;
}

Update 4.5 23:30

上面的代码跑了1890ms1890ms1890ms,我们觉得它不够优秀,于是冷静分析一下它慢在哪里,发现全1的数据就可以把它卡满,这是为什么呢?这是因为我们在长长的没有0的段里面做了很多次query,这其实是不必要的,我们同样可以预处理以每个位置开始的最长段。这样就可以O(1)O(1)O(1)计算整个段的贡献。

我们提交后发现它只跑了249ms249ms249ms

理性分析一下它的时间复杂度,相当于整个序列被01分割为若干段,跳到一个段就可以O(1)O(1)O(1)计算答案并且O(log⁡n)O(\log n)O(logn)跳到下一个段,这样每一段会有O(len)O(len)O(len)次贡献(因为每一个长度大于iii的段才可能产生贡献),每次贡献为O(log⁡n)O(\log n)O(logn),因此总时间复杂度为O(nlog⁡n)O(n\log n)O(nlogn)

Updated code

//线段树部分同上
signed main() {
#ifndef ONLINE_JUDGE
	freopen("a.in", "r", stdin);
#endif
	int n, cnt0 = 0, cnt1 = 0;
	read(n), reads(st);
	for (int i = 1, l = 1; i <= n ; ++ i) {
		auto add = [&](char x, int y) {
			if (x == '0') cnt0 += y; 
			if (x == '1') cnt1 += y;
		};
		add(st[i], 1);
		while (cnt0 && cnt1) add(st[l ++], -1);
		a[i] = i - l + 1;
	}
	cnt0 = 0, cnt1 = 0;
	for (int i = n, r = n; i >= 1 ; -- i) {
		auto add = [&](char x, int y) {
			if (x == '0') cnt0 += y; 
			if (x == '1') cnt1 += y;
		};
		add(st[i], 1);
		while (cnt0 && cnt1) add(st[r --], -1);
		b[i] = r - i;
	}
	build(1, 1, n);
	for (int i = 1; i <= n ; ++ i) {
		int cnt = 0, r = 1;
		for (; r <= n ;) {
			r = query(1, 1, n, r, i);
			if (r == -1) break;
			int p = b[r] / i;
			cnt += p + 1, r += i * (p + 1);
		}
		print(cnt), putc(' ');
	}
	return 0;
}

提供了一个基于51单片机的RFID门禁系统的完整资源文件,包括PCB图、原理图、论文以及源程序。该系统设计由单片机、RFID-RC522频射卡模块、LCD显示、灯控电路、蜂鸣器报警电路、存储模块和按键组成。系统支持通过密码和刷卡两种方式进行门禁控制,灯亮表示开门成功,蜂鸣器响表示开门失败。 资源内容 PCB图:包含系统的PCB设计图,方便用户进行硬件电路的制作和调试。 原理图:详细展示了系统的电路连接和模块布局,帮助用户理解系统的工作原理。 论文:提供了系统的详细设计思路、实现方法以及测试结果,适合学习和研究使用。 源程序:包含系统的全部源代码,用户可以根据需要进行修改和优化。 系统功能 刷卡开门:用户可以通过刷RFID卡进行门禁控制,系统会自动识别卡片并判断是否允许开门。 密码开门:用户可以通过输入预设密码进行门禁控制,系统会验证密码的正确性。 状态显示:系统通过LCD显示屏显示当前状态,如刷卡成功、密码错误等。 灯光提示:灯亮表示开门成功,灯灭表示开门失败或未操作。 蜂鸣器报警:当刷卡或密码输入错误时,蜂鸣器会发出报警声,提示用户操作失败。 适用人群 电子工程、自动化等相关专业的学生和研究人员。 对单片机和RFID技术感兴趣的爱好者。 需要开发类似门禁系统的工程师和开发者。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值