• HNOI2019 JOJO


    HNOI2019 JOJO

    被鱼那题送退役了,很生气。

    然后我Day1快下考的时候口胡了一个做法

    今天想起来之后就写了一下,发现它过了,它过了,它过了。

    woc要是不写鱼,我可以多出3个小时写T2,T3,随便打也能进队啊啊啊啊啊啊

    好了,不扯了,我们言归正传。

    我们发现,若两个串匹配,那么他们中间的每一段的长度也必须相同,只有第一段和最后一段可能不是完整的一段,因此我们可以直接将每一段相同的字符看成一个新的字符(我们暂且把新的字符叫做字段),求next数组。

    由于其中一个匹配串必须是整个串的前缀,因此,第一字段的处理几乎与一般的KMP相同,只有一种特殊情况:若某个字段与第一字段的字符相同,并且长度大于第一字段,也是可以匹配的,因为你可以用这一段的后面一段字符去匹配第一段字符

    比如说 对于串aabbbaabbaaabbb,我们可以把它看成 a2 b3 a2 b2 a3 b3 共6个字段的串

    这个新串的next数组为 0 0 1 0 1 2

    此时(next[4]=0)而不是(2),因为后一个字符一定与(b)不同,一定匹配不上下一个字符,这个next对于后面的匹配来说是无意义的,所以我们可以近似的认为,b2与b3不能匹配,但是在统计b2这段字符的答案会出现问题,因为实际上这一段是可以与前面的匹配的,所以我们需要在跳next的过程中,顺便将每个字符的next求出来计入答案。

    以上是我的考场做法,可以获得50分,但由于一些奇怪的错误我只获得了20分。

    贴下50分代码

    #include<bits/stdc++.h>
    typedef long long ll;
    using namespace std;
    const int _=1e5+5,M=2e4,yl=998244353;
    void inc(int &x,int y){x+=y;if(x>=yl)x-=yl;}
    int n;
    namespace task1{
        struct Node{
            int a[305],b[305],f[305],len,ans;
            int js(ll l,ll r){
                if(l>r)return 0;
                return (l+r)*(r-l+1)/2%yl;
            }
            void insert(int x){
                if(len==0)return ans=js(1,x%M-1),b[1]=x%M,f[1]=0,a[++len]=x,void();
                ll k=f[len],mx=0; a[++len]=x;
                while(1){
                    if(a[k+1]/M==x/M){
                        ll l=min(x%M,a[k+1]%M);
                        inc(ans,js(b[k]+mx+1,b[k]+l));
                        mx=l;
                    }
                    if(a[k+1]==x||!k)break; k=f[k];
                }
                if(a[k+1]==x)f[len]=k+1,b[len]=b[k]+x%M;
                else if(a[1]/M==x/M&&a[1]%M<x%M)
                    f[len]=1,inc(ans,max(0ll,(x%M-mx))*b[1]%yl);
                b[len]=b[len-1]+x%M;
            }
        }T[305];
        void main(){
            for(int i=1;i<=n;++i){
                int opt,x;cin>>opt;
                if(opt==1){
                    char c;cin>>x>>c;
                    T[i]=T[i-1];
                    T[i].insert(c*M+x);
                    cout<<T[i].ans<<endl;
                }
                else cin>>x,T[i]=T[x],cout<<T[i].ans<<endl;
            }
        }
    }
    namespace task2{
        int a[_],b[_],f[_],len,ans;
        ll js(ll l,ll r){
            if(l>r)return 0;
            return (l+r)*(r-l+1)/2%yl;
        }
        void insert(int x){
            if(len==0)return ans=js(1,x%M-1),b[1]=x%M,f[1]=0,a[++len]=x,void();
            ll k=f[len],mx=0; a[++len]=x;
            while(1){
                if(a[k+1]/M==x/M){
                    ll l=min(x%M,a[k+1]%M);
                    inc(ans,js(b[k]+mx+1,b[k]+l));
                    mx=l;
                }
                if(a[k+1]==x||!k)break; k=f[k];
            }
            if(a[k+1]==x)f[len]=k+1,b[len]=b[k]+x%M;
            else if(a[1]/M==x/M&&a[1]%M<x%M)
                f[len]=1,inc(ans,max(0ll,(x%M-mx))*b[1]%yl);
                //我考场上把b[1]写成了a[1]丢了30分
            b[len]=b[len-1]+x%M;
        }
        void main(){
            for(int i=1;i<=n;++i){
                int opt,x;cin>>opt;
                char c;cin>>x>>c;
                insert(c*M+x);
                cout<<ans<<endl;
            }
        }
    }
    int main(){
        ios::sync_with_stdio(false);
        cin>>n;
        if(n<=300)task1::main();
        else task2::main();
    }
    
    

    至于扩展到100分。

    但是暴力跳next的复杂度是均摊的,我们还是不能接受。

    因此我们考虑建立一个大概可以叫做KMP自动机的东西,由于经过转化后字符集很大,我们考虑通过可持久化线段树,来维护这个KMP自动机的转移,每次只需要将一个字符的next的转移拷贝过来,并把后一个字段加进转移即可。

    再考虑如何统计答案,我们发现,假设有一个字段是长度为L,那么每次匹配到它的另一个字段的前L个字符会被它匹配掉,而我们要求匹配最长,所以我们只需要找到最后一段可以匹配的,依然考虑通过可持久化线段树维护一类字段的第(i)个能匹配多长的字符。每次需要进行的操作就变成了前缀赋值与前缀求和。

    因此我们就可以通过可持久化线段树的,单点修改,前缀赋值,前缀求和来完成所有1操作。

    而这样做可以顺便解决2操作带来的可持久化的问题。

    时空复杂度均为(O(nlog n))

    代码就不贴了,有需要的可以私信我。

  • 相关阅读:
    Java输出文件到本地(输出流)
    Java 工厂设计模式
    实际工作与JAVA面试题
    JAVA 转义字符串中的特殊字符
    Oracle工作笔记
    JS验证表单中TEXT文本框中是否含有非法字符
    JAVA 解析TXT文本
    表单异步提交数据
    rem.js(2)
    rem.js(1)
  • 原文地址:https://www.cnblogs.com/ljq-despair/p/10673009.html
Copyright © 2020-2023  润新知