题目背景
由于Y校的老师非常毒瘤,要求(zhouwc)在(csp)考前最后(3)天参加期中考,(zhouwc)非常生气,决定消极考试,以涂完卡但全错为目标。现在(retcarizy)看(zhouwc)太可怜了,想要帮(zhouwc)解决一个问题,但他自己又太忙了,咕咕咕,于是就把问题甩给了你。
题目描述
给你一个长度为(n)的字符串(S)。
有(m)个操作,保证(mleq n)。
你还有一个字符串(T),刚开始为空。
共有两种操作。
第一种操作:
在字符串(T)的末尾加上一个字符。
第二种操作:
在字符串(T)的开头加上一个字符。
每次操作完成后要求输出有几个(l in [1,T.size])满足以下条件:
对于(forall i in [1,l])有(T_{T.size-l+i} e S_{i})
(Tip:)字符串下标从(1)开始。(T.size)表示(T)的长度。
输入格式
第一行两个正整数(n,m)。
第二行(n)个正整数,用空格隔开,第(i)个整数表示(S_i)。
接下来(m)行,每行两个数字(opt,ch),(opt=0)表示在(T)的末尾加一个字符(ch),(opt=1)表示在(T)的开头加一个字符(ch)。
输出格式
共(m)行,每行一个非负整数表示第(m)操作后的输出。
输入输出样例
输入 #1
10 3
1 2 3 1 2 3 2 3 2 3
0 1
1 2
0 3
输出 #1
0
1
1
说明/提示
注意:本题采用捆绑测试,只有当你通过一个subtask的所有点后,你才能拿到这个subtask的分数
对于所有的数据 (n leq 10^6,m leq 3.3333 imes 10^4,|sum|leq10^3,S_i in [1,|sum|])((sum)表示字符集)
(subtask1(17\%)):(m leq 333)
(subtask2(33\%):m leq 3333)
(subtask3(20\%):|sum|leq2∣)
(subtask4(30\%):)无特殊条件
样例解释:
第一次操作后,(T=“1”),
(l=1)时(T[1]=S[1]),所以答案为你不jjgvfj(0)
第二次操作后,(T=“21”),
(l=1)时,(T[2]=S[1])
(l=2)时,(T[1]!=S[1]),(T[2]!=S[2]),所以答案为(1)
第三次操作后,(T=“213”),
(l=1)时,(T[3]!=S[1]);
(l=2)时,(T[2]=S[1]);
(l=3)时,(T[3]=S[3]),所以答案为(1)
(O(m^3))的做法很容易想,按照题意模拟即可。预计得分(17pts)。
对于(O(m^2))的做法,因为这个题实际上是查找(S)的前(l)个和(T)的后(l)个是否严格不相等,我们考虑记录(dp[l])表示在上述意义下(l)是否合法。容易知道,在(T)串最后插入一个字符时,因为(S)串始终不变,(T)串的最后(l)个字符从原本(T)串的后(l)个字符变成了原本(T)串的后(l-1)个字符加上新加入的字符,所以为了比较新的(T)串后(l)个字符是否合法,我们只需要比较新字符、原本(T)串的后(l-1)个字符是否相等即可。即(dp[i]=dp[i-1]|(ch==S[i]))。这样,对于每个加入的字符,只需要用(O(1))的复杂度检查每个枚举到的(l)是否合法即可。
在(T)串最前面插入一个字符时,因为原本所有的合法的(l)依然没有变化,只是增加了一个新的(l),所以我们只需暴力(check)新加入的答案(l),对于每一位枚举是否不同即可。
时间复杂度(O(m^2)),预计得分(50pts)。是我在考场上想出来的方法。
考虑优化(O(m^2))的做法,我们找到了状压神器——(bitset),它可以将复杂度优化到原来的(frac{1}{32})。如果常数优秀一些这个方法可以过。
考虑刚才的方法算过了哪些不可能合法的状态,我们知道所有的字符其存在位置都是独立的,所以我们用一个(bitset)数组(id[i])记录字符(i)在哪些位置上出现过。只要加入的新数(dt)对应的位置是(id[dt])上(1)的位置,则该状态肯定不合法。
所以这样优化的关键在于同时算出了所有合法的状态。所以我们用(f)的第(i)位的(0/1)表示后缀长度为(i)时是否合法。
如果在(T)串尾部加入新的字符,则对于长度是(i)的情况一定是由(i-1)的情况和新加入位的情况同时转移来(见上述(O(n^2))做法),而所有新加入的位对应与(S)串中哪些位相同已经存储好,假设加入的字符是(dt),则(f=(f<<1)|id[dt])。
如果在(T)串头部加入新的字符,设原来(T)串有效的后缀长度有(l)位,则新的(T)串后(l)位是否合法状态不变,所以新旧(T)串前面(l)位答案一样;
在(T)串头部插入新字符时,我们发现遇到了一些新的问题:
第一,我们发现在头部加入字符时,后面的所有字符都往后移了一位。
第二,我们需要比较加入的新字符和第一个字符是否相同。
很明显困难在于解决第一个问题。因为我们如果要想比较移动之后的字符和(S)的关系,在不知道其它任何东西的情况下,需要另用一个(O(n))检查。
解决这个问题的方法是一个非常重要的思想:费用提前。在每次从队尾加入一个字符时,我们将这个字符所能贡献到答案的所有位置一次存好。方法很简单,假设我们每次加进的字符是(dt),考虑这一位对应到(S)串的所有可能。如果(dt)对应到的某一位上(id[dt])在同样的位上恰好是(1),说明当队尾不断加入字符使当前这个(dt)恰好对应到刚才说的这一位上,则这样的方案肯定是不合法的。
考虑如何进行这样的操作。假设(dt)是在第(i)位加入队列,则(dt)离(T)结尾的长度是(i-1)。注意这里我们只讨论(T)序列结尾的费用提前,因为其它点情况和结尾一样。假设(dt)对应的(id)值在第(k)位上是(1),说明(dt)在取到第(k)位时整体一定不合法。这时(dt)距离队尾的距离是(l-1),所以(dt)的位置由队尾左移(i-1)位得到,所以当(dt)取到(k)时,队尾应该取到(k+l-1)位,但是注意是反着存的,所以:
上代码:
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<bitset>
using namespace std;
int n,m,opt,S[1000005],dt;
bitset<35005> f,id[1005],now;
int read(){
int ans=0;
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch<='9'&&ch>='0'){
ans=ans*10+ch-'0';
ch=getchar();
}
return ans;
}
int main(){
n=read();
m=read();
for(int i=1;i<=n;i++)
S[i]=read();
for(int i=1;i<=m;i++)
id[S[i]].set(i);
now.set();
for(int i=1;i<=m;i++){
opt=read();
dt=read();
now.reset(i);
if(opt==0)
f=(f<<1)|id[dt];
else
f=f|(id[dt]<<(i-1));
printf("%d
",(~(f|now)).count());
}
return 0;
}