洛谷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;
}