[luogu T71973]卡常者π醬

[luogu T71973]卡常者π醬

題意

給定一個長度爲 \(n\) 的字符串, 要求將字符串分割爲若干段, 每一段要麼是一個字符要麼是前面幾段的並的子串.c++

若是某一段是一個單獨字符, 則產生 \(a\) 的開銷.curl

若是是前幾段的並的子串, 則產生 \(b\) 的開銷.優化

若是知足兩個條件, 則能夠在 \(a,b\) 中任選一個開銷.url

求劃分的最小開銷.spa

\(n\le 5\times 10^6\), 字符集大小 \(\Sigma\le 7\).3d

題解

冷靜分析一下發現是沙雕題code

然而題目說不卡常其實是真的卡常...嚴格 \(O(n)\) 並不必定能跑過...blog

咱們發現這個沙雕題最後一個串的開銷和前面的劃分方案無關, 果斷想到DP. 設 \(dp_i\) 表示長度爲 \(i\) 的前綴的最小劃分代價.排序

而後有個顯然的性質, 就是若是有一個後綴知足它在前面出現過, 那麼這個後綴的全部後綴一樣都知足. 因此不難想到對於當前DP前綴找到知足該性質的最長的後綴, 而後在這個後綴的全部後綴中找最小DP值更新.隊列

不難發現這個過程實際上能夠用後綴自動機解決. 對於每一個狀態都維護一下 \(right\) 集合中的最小值, 也就是當前狀態所表明字符串的第一次出現的右端點的位置. 這樣咱們就能夠直接知道當先後綴是否在前面出現過了.

不難發現這個後綴的左端點位置也是單調的, 因此不合法暴力跳就能夠 \(O(n)\) 了.

按照剛剛的討論咱們須要把全部合法的後綴的 \(dp\) 值取 \(\min\), 須要單調隊列來維護. 可是實際上不難發現 \(dp\) 值是單調不降的, 因此直接取最長合法後綴的左端點處的 \(dp\) 值就能夠了.

可是要想A這題還得加點優化. 一個是 \(right\) 集合的最小值的維護, 並不須要給後綴自動機結點基數排序. 由於每次插入的新點的 \(right\) 集合中的值其實是單調遞增的, 並且若是在原來自動機上某個點在另外一個點的子樹中的話擴展後必定還在那個點的子樹裏. 因此能夠邊構造邊算.

其次是咱們並不須要先構造出整個SAM而後再跑, 咱們徹底能夠邊構造邊計算最長知足條件的後綴. 由於後面的字符串並不會影響前面已有的信息.

加了這兩個優化才卡時限過的...這可真蠢.

(不難分析獲得不加兩個優化+單調隊列實際上也是嚴格 \(O(n)\) 的時間複雜度)

參考代碼

#include <bits/stdc++.h>

const int MAXN=1e7+10;
typedef long long intEx;

int n;
int a;
int b;
int cnt=1;
int last=1;
int root=1;
int s[MAXN];
int buc[MAXN];
int len[MAXN];
int prt[MAXN];
int minr[MAXN];
char str[MAXN];
intEx dp[MAXN];
int chd[MAXN][7];

void Extend(char);

int main(){
    scanf("%d%d%d",&n,&a,&b);
    scanf("%s",str+1);
    int cur=root,curlen=0;
    for(int i=1;i<=n;i++){
        Extend(str[i]);
        int p=str[i]-'a';
        while(!chd[cur][p]){
            cur=prt[cur];
            curlen=len[cur];
        }
        ++curlen;
        cur=chd[cur][p];
        while(cur!=root&&minr[cur]>i-curlen){
            if(i-minr[cur]>len[prt[cur]])
                curlen=i-minr[cur];
            else{
                cur=prt[cur];
                curlen=len[cur];
            }
        }
        dp[i]=dp[i-1]+a;
        if(curlen)
            dp[i]=std::min(dp[i],dp[i-curlen]+b);
    }
    printf("%lld\n",dp[n]);
    return 0;
}

void Extend(char ch){
    int x=ch-'a';
    int p=last;
    int np=++cnt;
    last=np;
    minr[np]=len[np]=len[p]+1;
    while(p&&!chd[p][x])
        chd[p][x]=np,p=prt[p];
    if(!p)
        prt[np]=root;
    else{
        int q=chd[p][x];
        if(len[q]==len[p]+1)
            prt[np]=q;
        else{
            int nq=++cnt;
            memcpy(chd[nq],chd[q],sizeof(chd[q]));
            len[nq]=len[p]+1;
            prt[nq]=prt[q];
            prt[q]=nq;
            prt[np]=nq;
            minr[nq]=minr[q];
            while(p&&chd[p][x]==q)
                chd[p][x]=nq,p=prt[p];
        }
    }
}

相關文章
相關標籤/搜索