「bzoj 4180: 字符串計數」
阿新 • • 發佈:2019-03-13
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: 字符串計數」