1. 程式人生 > >[CTSC2012]熟悉的文章(字尾自動機+動態規劃)

[CTSC2012]熟悉的文章(字尾自動機+動態規劃)

題目描述

阿米巴是小強的好朋友。

在小強眼中,阿米巴是一個作文成績很高的文藝青年。為了獲取考試作文的真諦,小強向阿米巴求教。阿米巴給小強展示了幾篇作文,小強覺得這些文章怎麼看怎麼覺得熟悉,彷彿是某些範文拼拼湊湊而成的。小強不禁向阿米巴投去了疑惑的眼光,卻發現阿米巴露出了一個狡黠的微笑。

為了有說服力地向阿米巴展示阿米巴的作文是多麼讓人覺得“眼熟”,小強想出了一個評定作文 “熟悉程度”的量化指標:L 0 .小強首先將作文轉化成一個 01 串。之後,小強蒐集了各路名家的文章,同樣分別轉化成 01 串後,整理出一個包含了 M 個 01 串的“ 標準作文庫 ”。

小強認為:如果一個 01 串長度不少於 L 且在 標準作文庫 中的某個串裡出現過(即,它是 標準作文庫 的 某個串 的一個 連續子串 ),那麼它是“ 熟悉 ”的。對於一篇作文(一個 01 串)A,如果能夠把 A 分割成若干段子串,其中“ 熟悉 ” 的子串的 長度 總 和 不少於 A 總 長度的 90%,那麼稱 A 是 “ 熟悉的文章 ”。 L 0 是 能夠讓 A 成為 “ 熟悉的文章 ” 的 所有 L 的最大值 (如果不存在這樣的 L,那麼規定 L 0 =0)。

舉個例子:

小強的作文庫裡包含了如下 2 個字串:

10110
000001110

有一篇待考察的作文是:

1011001100

小強計算出這篇作文 L 的最大值是 4,因為待考察的作文可以視作'10110'+'0110'+'0',其中'10110'和'0110'被判定為 “ 熟悉 ” 的。而當 L = 5 或是更大的時候,不存在符合題意的分割方法。所以,這篇作文的 L 0 = 4。小強認為阿米巴作文的 L 0 值比其他同學的明顯要大。請你幫他驗證一下。

題解

我們可以對模式串建廣義SAM,求出文字串的每個字首與模式串的最長公共字尾。

這玩意有什麼用?

再繼續考慮,答案具有單調性,我們可以外面套個二分。

然後又轉移方程

dp[i]=max(dp[i-1],dp[j]+i-j)(i-LCS<=j<=i-mid)

很明顯,轉移是一個區間,而且這個區間是向右滑動的,所以可以直接上單調佇列。

程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2200009
using namespace std;
int l[N],last,len,ch[N][2],cnt,fa[N],q[N],h,t,dp[N],g[N],le[N],n,m;
char s[N];
inline void insert(int
x){ if(!ch[last][x]){ int p=last,np=++cnt;l[np]=l[p]+1;last=np; for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;//?? if(!p)fa[np]=1; else{ int q=ch[p][x]; if(l[p]+1==l[q])fa[np]=q; else{ int nq=++cnt;l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq; } } } else{ int p=last,q=ch[last][x]; if(l[p]+1==l[q])last=q; else { int nq=++cnt;l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=nq;//!! for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq; last=nq; } } } inline void ins(int x){ while(h<=t&&g[q[t]]<=g[x])t--; q[++t]=x; } inline bool check(int mid,int n){ h=1,t=0; for(int i=0;i<=n;++i)g[i]=dp[i]=0; for(int i=1;i<=n;++i){ dp[i]=dp[i-1]; int ll=i-le[i],rr=i-mid;ll=min(ll,rr+1); if(rr>=0)ins(rr); while(h<=t&&q[h]<ll)h++; if(h<=t)dp[i]=max(dp[i],g[q[h]]+i); g[i]=dp[i]-i; } return dp[n]>=n*0.9; } int main(){ scanf("%d%d",&n,&m);cnt=1; for(int i=1;i<=m;++i){ scanf("%s",s+1);len=strlen(s+1);last=1; for(int j=1;j<=len;++j)insert(s[j]-'0'); } while(n--){ scanf("%s",s+1);len=strlen(s+1); int now=1; for(int i=1;i<=len;++i){ if(ch[now][s[i]-'0'])now=ch[now][s[i]-'0'],le[i]=le[i-1]+1; else{ while(now&&!ch[now][s[i]-'0'])now=fa[now]; if(now)le[i]=l[now]+1,now=ch[now][s[i]-'0'];else now=1; } } int L=1,R=len,ans=0; while(L<=R){ int mid=(L+R)>>1; if(check(mid,len))ans=mid,L=mid+1;else R=mid-1; } printf("%d\n",ans); } return 0; }