洛谷P5640 【CSGRound2】逐梦者的初心 题解

【CSGRound2】逐梦者的初心

题目链接:【CSGRound2】逐梦者的初心 - 洛谷
参考:状压DP复习笔记 by Amaurëa.

思路

(说个题外话,这题我看了一眼立马觉得是 bitset,然而太蒻了推了会发现没啥思路放弃了,结果真的是 bitset qwq)。

首先手推一下(似乎并不需要)就能发现:

在字符串 \(T\) 后添加一个字符,相当于是把 \(T\) 的后缀整体前移了一位,再在后缀尾部增加这个字符。

相对的,往 \(T\) 前添字符自然就是把 \(T\) 的后缀整体后移了一位,再在后缀前部增加这个字符。

(这个时候位运算的雏形已经显现出来了)

算法设计

  • 算法 1:暴力

不难发现,如果往前添加一个字符,那么原先的合法后缀仍然合法,只需要判断这个新加入的后缀即可。

如果往后添加一个字符,原来不合法的仍然不合法,合法的不一定合法,这时就需要重新匹配。

那么用一个 vector 把合法后缀编号存一下,然后按照上面的思路暴力匹配就行了。

时间复杂度 \(O(M \times \text{我也不知道多少})\)

代码找不到了 QAQ。

  • 算法 2:bitset

根据上述分析,不难发现,这个玩意的匹配可以转换成位运算(有左移右移操作)。

那么就可以考虑用 bitset 处理。

可以发现,字符串 \(S\) 只有前 \(m\) 位是我们需要的。

我们可以开 \(|\sum|\) 个 bitset,第 \(i\) 个 bitset \(cnt_i\) 记录字符串 \(S\)\(i\) 出现的位置。

再开一个 bitset \(f\) ,处理当前的匹配状态,设处理到第 \(i\) 个操作。

然后在处理 \(T\) 时,当前操作若为 \(0\) ,则后缀整体左移一位,即 \(f << 1\),然后再在后面或上 \(cnt_{ch}\)

若为 \(1\),则后缀不动,\(cnt_{ch}\) 左移 \(i-1\) 位,再和 \(f\) 相或。

\(f\) 中,若第 \(x\) 位为 \(1\) ,则说明当 \(T\) 中的某个字符与 \(S\) 串的第 \(x\) 位匹配时,两者相同,不合法。

这样说非常抽象,可以看代码理解,自己推一推就明白了。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <cctype>
using namespace std;
const int maxn = 1000005;
const int maxm = 3e4 + 4e3;
const int maxsize = 1e3 + 5;
int n,m,s[maxn];
bitset<maxm> f,cnt[maxsize],avail;
int read() {
	int s = 0;
	char c = getchar();
	for(;!isdigit(c);c = getchar());
	for(;isdigit(c);c = getchar())s = (s << 1) + (s << 3) + (c ^ '0');
	return s;
}
int main() {
	n = read();
	m = read();
	for(int i = 1;i <= n;++ i)s[i] = read();
	for(int i = 1;i <= m;++ i)cnt[s[i]].set(i , 1);
	avail.set();
	for(int i = 1;i <= m;++ i) {
		int op = read();
		int x = read();
		avail.reset(i);//avail 是为了限制前 i 位,不然会统计多余答案
		if(!op)f = (f << 1) | cnt[x];
		else f = f | (cnt[x] << (i - 1));
		printf("%d\n",(~(f | avail)).count());
	}
	return 0;
}
posted @ 2022-01-14 16:45  ImALAS  阅读(60)  评论(0编辑  收藏  举报