[JZOJ3484]密码
题目大意:
给你一个很长的字符串a(|a|<=300000),一个比较短的字符串b(|b|<=200),请你搞一些破坏。
你可以从a的两边去掉一些字符使得b仍是a的一个字串,问有多少种方案?
思路:
首先预处理一下串a,用f[i][j]记录对于第i个位置的字符,左边最靠近i的字符j在哪里。
然后枚举每一个字符作为最后一个字符,往前跳,如果把整个b串跳完了,就说明减掉这两边的都没关系,计入方案。
设a的范围为(1,n),b的范围为(i,j)那么答案增加i*(n-j+1)。
然而这样会重复算很多,不去重直接爆零了。
考虑如何去重。
记录一下上次找到的序列最右边的端点在哪里,记为last,答案增加i*(last-j)。
1 #include<cstdio> 2 #include<cstring> 3 typedef long long int64; 4 const int N=300002,M=202; 5 char s[N],t[M]; 6 int f[N][26]; 7 inline int idx(const char &ch) { 8 return ch-'a'; 9 } 10 int main() { 11 scanf("%s%s",s+1,t+1); 12 int n=strlen(s+1),m=strlen(t+1); 13 for(register int i=1;i<n;i++) { 14 memcpy(f[i+1],f[i],sizeof *f); 15 f[i+1][idx(s[i])]=i; 16 } 17 int64 ans=0; 18 int last=n+1; 19 for(register int i=n;i;i--) { 20 if(s[i]!=t[m]) continue; 21 for(register int j=m,k=i,l;j&&k;k=f[l=k][idx(t[--j])]) { 22 if(j==1) { 23 ans+=k*(last-i); 24 last=i; 25 } 26 } 27 } 28 printf("%lld\n",ans); 29 return 0; 30 }