CF1968G2.Division+LCP-字串、Z函式

yoshinow2001發表於2024-05-03

link:https://codeforces.com/problemset/problem/1968/G2
給一個字串 \(s\),定義 \(f_k\) 表示:將 \(s\) 劃分成恰好 \(k\)\(w_1,\dots,w_k\) 之後, \(LCP(w_1,\dots,w_k)\)$ 的最大值,其中LCP表示最長公共字首。求 \(f_l,\dots,f_r\) (其實和求所有 \(f\) 沒什麼區別)
\(\sum |s|\leq 2\times 10^5\).


對於這樣的劃分問題,首先需要注意到的(顯然的事實)是: \(s\)字首一定會作為某個 \(w_i\) 出現,因此LCP也一定是 \(s\) 的字首。

那麼就可以想,暴力列舉 \(s\) 的字首 \(s[1,\dots,k]\),如果其至多能在 \(s\) 當中不相交地出現 \(c\) 次,那麼 \(f_1,\dots,f_c\) 都至少是 \(k\).
判斷 \(s\) 字首在 \(s\) 中不相交地出現幾次,直接用KMP是穩定 \(O(n)\) 的,對每個 \(k\) 處理就退化成 \(O(n^2)\) 了。
難道會是什麼神奇的字尾結構嗎(並不會字尾xxx系列…),可惡,難道就到此為止了嗎…

不可以,div3不會是這樣的難度,讓我們來想點稍微簡單的東西,KMP麻煩就麻煩在要把整個串跑一遍,而我們知道列舉字首 \(k\) 的話,至多有 \(O(n/k)\) 段,這裡其實差了很多,也許可以嘗試從這裡想。如果寫雜湊的話,要怎麼從當前一段快速跳到下一段,每次查詢的字首都不一樣,用雜湊也只能把整個串掃一遍…

等一下!Z函式是求什麼來著的? \(z_i\) 能處理 \(s\)\(s[i,\dots,n]\) 的LCP,如果用Z函式來跳,假設當前在位置 \(i\),那隻要在 \([i+1,n]\) 當中找到最小的 \(j\) 使得 \(z_j\geq k\) ,然後把 \(i\) 跳到 \(j\) 就可以了,尋找 \(j\) 的過程可以用靜態RMQ+二分完成,這樣複雜度就是 \(O(\log n\times (n/k))\) 了,對每個 \(k\) 跑一遍,算出出現次數 \(c\) ,然後給 \(f_1,\dots f_c\) 做一個取max的操作,字首取max也可用類似字首和那樣透過打標記完成。

總複雜度 \(O(n\log^2 n)\).

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define endl '\n'
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N=2e5+5;
vector<int> exKMP(const string &s){
    int n=s.length();
    vector<int> z(n);
    for(int i=1,l=0,r=0;i<n;++i){
        if(i<=r&&z[i-l]<r-i+1)z[i]=z[i-l];
        else{
            z[i]=max(0,r-i+1);
            while(i+z[i]<n&&s[z[i]]==s[i+z[i]])++z[i];
        }
        if(i+z[i]-1>r)l=i,r=i+z[i]-1;
    }
    return z;
}

int n,l,r,tag[N];
int f[21][N],Log[N];
string s;
int query(int l,int r){
    int k=Log[r-l+1];
    return max(f[k][l],f[k][r-(1<<k)+1]);
}
int main(){
    fastio;
    Log[0]=-1;
    rep(i,1,N-5)Log[i]=Log[i>>1]+1;
    int tc;cin>>tc;
    while(tc--){
        cin>>n>>l>>r>>s;
        auto z=exKMP(s);
        rep(i,0,n-1)f[0][i]=z[i];
        rep(i,0,n)tag[i]=0;
        rep(j,1,20)for(int i=0;i+(1<<j)-1<n;i++)
            f[j][i]=max(f[j-1][i],f[j-1][i+(1<<(j-1))]);
        auto work=[&](int k){
            int cnt=1;
            for(int i=0;i<n;){
                int L=i+k,R=n-1,ret=-1;
                while(L<=R){
                    int mid=(L+R)>>1;
                    if(query(i+k,mid)>=k){
                        ret=mid;
                        R=mid-1;
                    }else L=mid+1;
                }
                if(ret==-1)break;
                cnt++;
                i=ret;
            }
            tag[cnt]=max(tag[cnt],k);
        };
        rep(i,1,n)work(i);
        tag[n+1]=0;
        for(int i=n;i>=1;i--)tag[i]=max(tag[i],tag[i+1]);
        rep(i,l,r)cout<<tag[i]<<' ';
        cout<<endl;
    }
    return 0;
}

相關文章