1. 程式人生 > >「bzoj 4180: 字符串計數」

「bzoj 4180: 字符串計數」

line class ins memset gis inf int 字符 根據

題目

真是一道好題

首先根據一個非常顯然的貪心,如果給出了一個串\(S\),我們如何算最小操作次數呢

非常簡單,我們直接把\(S\)拉到\(T\)\(SAM\)上去跑,如果跑不動了就停下來,重新回到\(1\)繼續跑

於是我們建出一個\(SAM\)之後可以寫一個這樣的暴力,設\(d[i][j][k]\)表示從\(i\)點到\(j\)點走\(i\)條邊的最長路,對於那些走不動的邊,我們可以接到\(1\)號節點對應的出邊上去,邊權為\(1\),其余的邊權為\(0\),矩陣優化一下就是\(O(|T|^3logn)\)的復雜度

顯然\(|T|\)並不允許我們開下如此之大的轉移矩陣,嘗試換一個角度來考慮這個問題

我們發現我們問題的本質就是最大化最小值,這是不是可以二分一下呢

於是現在的問題變成了對於一個二分出的操作次數\(mid\),判斷答案是否能夠更大

顯然我們如果使用\(mid\)此操作構造出來的串長度小於\(n\),那麽我們就可以斷定答案可能會更大一些

於是又把問題轉化成了利用\(mid\)次操作構造出來的字符串的最小長度

這個如何求呢,我們考慮一次操作無非就是從\(1\)的某一個出邊指向的節點到另一個\(1\)的出邊指向的節點,所以我們求出這些節點兩兩之間的最短路就好了

於是我們現在又可以利用矩陣轉移了,復雜度\(O(|T|+|c|^3log^2n)\),\(|c|\)為字符集大小

代碼

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define LL long long
#define re register
#define inf 922337203685477580
#define maxn 200005
LL n;
char S[maxn>>1];
int m,lst=1,cnt=1,tot;
int len[maxn],fa[maxn],son[maxn][4],q[maxn],vis[maxn],c[maxn];
LL a[4][4],ans[4][4],t[4][4],d[maxn];
inline void ins(int c) {
    int p=++cnt,f=lst;lst=p;
    len[p]=len[f]+1;
    while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
    if(!f) {fa[p]=1;return;}
    int x=son[f][c];
    if(len[f]+1==len[x]) {fa[p]=x;return;}
    int y=++cnt;len[y]=len[f]+1;
    fa[y]=fa[x],fa[x]=fa[p]=y;
    son[y][0]=son[x][0];son[y][1]=son[x][1];
    son[y][2]=son[x][2];son[y][3]=son[x][3];
    while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
inline void did_t() {
    LL mid[4][4];
    for(re int i=0;i<4;i++)
        for(re int j=0;j<4;j++) mid[i][j]=t[i][j],t[i][j]=inf;
    for(re int k=0;k<4;k++)
        for(re int i=0;i<4;i++)
            for(re int j=0;j<4;j++)
                t[i][j]=min(t[i][j],mid[i][k]+mid[k][j]);
}
inline void did_ans() {
    LL mid[4][4];
    for(re int i=0;i<4;i++) 
        for(re int j=0;j<4;j++) mid[i][j]=ans[i][j],ans[i][j]=inf;
    for(re int k=0;k<4;k++) 
        for(re int i=0;i<4;i++)
            for(re int j=0;j<4;j++)
                ans[i][j]=min(ans[i][j],mid[i][k]+t[k][j]);
}
inline LL solve(LL now) {
    for(re int i=0;i<4;i++)
        for(re int j=0;j<4;j++) t[i][j]=a[i][j],ans[i][i]=inf;
    for(re int i=0;i<4;i++) ans[i][i]=0;
    LL b=now;
    while(now) {if(now&1ll) did_ans();now>>=1ll;did_t();}
    LL tmp=inf;
    for(re int i=0;i<4;i++)
        for(re int j=0;j<4;j++) tmp=min(tmp,ans[i][j]);
    return tmp+b;
}
inline int check(LL now) {return solve(now)<=n;} 
int main() {
    scanf("%lld",&n);scanf("%s",S+1);m=strlen(S+1);
    for(re int i=1;i<=m;i++) ins(S[i]-'A');
    for(re int i=0;i<4;i++)
        for(re int j=0;j<4;j++) a[i][j]=inf;
    for(re int i=0;i<4;i++) {
        tot=0;q[++tot]=son[1][i];
        memset(vis,0,sizeof(vis));
        memset(d,20,sizeof(d));d[q[1]]=0;
        for(re int j=1;j<=tot;j++) {
            int x=q[j];
            for(re int k=0;k<4;k++) {
                if(vis[son[x][k]]) continue;
                if(!son[x][k]) a[i][k]=min(a[i][k],d[x]);
                else vis[son[x][k]]=1,d[son[x][k]]=d[x]+1,q[++tot]=son[x][k];
            }
        }
    }
    LL l=1,r=n,ans=0;
    while(l<=r) {
        LL mid=l+r>>1ll;
        if(check(mid)) l=mid+1,ans=mid;
            else r=mid-1;
    }
    if(solve(ans)<n) ans++;
    printf("%lld\n",ans);
    return 0;
}

「bzoj 4180: 字符串計數」