2019.11.11 洛谷月赛t3

题目背景

因为Y校的老师很是毒瘤,要求\(zhouwc\)\(csp\)考前最后\(3\)天参加期中考,\(zhouwc\)很是生气,决定消极考试,以涂完卡但全错为目标。如今\(retcarizy\)\(zhouwc\)太可怜了,想要帮\(zhouwc\)解决一个问题,但他本身又太忙了,咕咕咕,因而就把问题甩给了你。数组

题目描述

给你一个长度为\(n\)的字符串\(S\)测试

\(m\)个操做,保证\(m\leq n\)优化

你还有一个字符串\(T\),刚开始为空。spa

共有两种操做。code

第一种操做:队列

在字符串\(T\)的末尾加上一个字符。ip

第二种操做:字符串

在字符串\(T\)的开头加上一个字符。get

每次操做完成后要求输出有几个\(l \in [1,T.size]\)知足如下条件:string

对于\(\forall i \in [1,l]\)\(T_{T.size-l+i} \ne 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 \times 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\)位,可是注意是反着存的,因此:
\[ f=f|(id[dt]<<x-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\n",(~(f|now)).count());
    }
    return 0;
}
相关文章
相关标签/搜索