2019.11.11 洛谷月赛t3

题目背景

由于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)位,但是注意是反着存的,所以:

[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
",(~(f|now)).count());
    }
    return 0;
}
原文地址:https://www.cnblogs.com/qxds/p/11837850.html