P4022 [CTSC2012]熟悉的文章
题目
题目大意:多个文本串,多个匹配串,我们求\(L\),\(L\)指(匹配串中\(≥L\)长度的子串出现在文本串才为"熟悉",使得匹配串整个近似"熟悉")的最大值
近似"熟悉":将匹配串分割,所有串总"熟悉"长度有\(90\%\)以上
做法
首先明确一点,\(L_1<L_2\),则\(L_1\)的熟悉程度\(≥L_2\)的熟悉程度
比如文本串\('adc'\),匹配串\('ac'\),\(L=2\)熟悉程度为\(0\%\),\(L=1\)熟悉程度为\(100\%\)
故我们可以二分\(L\)然后去\(dp\),设数组\(dp_i\)为以\(i\)结尾前的熟悉长度
则:\(dp_i=max\{dp_j+(i-j)\}(j\in [i-match_i,i-L])\),其中\(match_i\)是以\(i\)结尾的后缀所能匹配的最长长度
\(i-match_i\)和\(i-L\)都是单调递增的,单调队列优化一下
\(O(nlogn)\)
My complete code
几个月前惨不忍睹的码风虽然现在依旧是,将就看吧
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=400000;
LL n,m,nod=1,last,Len;
LL len[maxn],son[maxn][26],fail[maxn],val[maxn],dp[maxn];
char s[maxn];
inline void Insert(LL c){
LL p=last,np=++nod;
last=np;
len[np]=len[p]+1;
while(p&&!son[p][c]){
son[p][c]=np;
p=fail[p];
}
if(!p)
fail[np]=1;
else{
LL q=son[p][c];
if(len[q]==len[p]+1)
fail[np]=q;
else{
LL nq=++nod;
len[nq]=len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fail[nq]=fail[q];
fail[q]=fail[np]=nq;
while(p&&son[p][c]==q){
son[p][c]=nq;
p=fail[p];
}
}
}
}
inline void Match(){
LL now=1,l=0;
for(LL i=1;i<=Len;++i){
LL c=s[i]-'0';
while(now&&!son[now][c]){
now=fail[now];
l=len[now];
}
if(now){
now=son[now][c];
++l;
}else{
now=1;
l=0;
}
val[i]=l;
}
}
inline bool check(LL L){
LL head=1,tail=0;
LL que[maxn];
for(LL i=1;i<=L-1;++i)
dp[i]=0;
for(LL i=L;i<=Len;++i){
while(head<=tail&&dp[que[tail]]-que[tail]<dp[i-L]-(i-L))
--tail;
que[++tail]=i-L;
while(head<=tail&&que[head]<i-val[i])
++head;
dp[i]=dp[i-1];
if(head<=tail)
dp[i]=max(dp[i],dp[que[head]]-que[head]+i);
}
return dp[Len]*10>=Len*9;
}
inline LL Solve(){
LL l=1,r=Len,ans=0;
Match();
while(l<=r){
LL mid=(l+r)>>1;
if(check(mid)){
ans=mid;
l=mid+1;
}else
r=mid-1;
}
return ans;
}
int main(){
scanf("%lld%lld",&n,&m);
while(m--){
scanf(" %s",s+1);
Len=strlen(s+1);
last=1;
for(LL i=1;i<=Len;++i)
Insert(s[i]-'0');
}
while(n--){
scanf(" %s",s+1);
Len=strlen(s+1);
printf("%lld\n",Solve());
}
}