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)\)

代碼就不貼了,有需要的可以私信我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM