题意:
给定长度为 (n) 的数字串 (s) 和长度为 (d) 的不含前导零的数字串 (x,y(x le y))。
求存在长度至少为 (leftlfloorfrac{d}{2} ight floor) 的子串是 (s) 的子串的数字串 (t in [x,y]) 的数量。
(n le 10^3),(d le 50),答案对 (10^9+7) 取模。
算一道挺难的题,质量也不错,适合练码力。
怎么表示一个长度为 (frac d2) 的串在s中出现,我们发现s长度并不是很大,1000,我们把s中所有长度大于等于 (frac d2) 的串一起建立一个AC自动机,然后发现只需要把长度等于 (frac d2) 的建AC自动机就好了,因为包含长度大于 (frac d2) 的串也就肯定包含等于 (frac d2) 的串。
然后把每个串的末尾点和该点fail子树上所有点打上标记,表示如果走到这个点,就能包含一个长度为 (frac d2) 的子串。
关于 (fail) 树的意义不想在这里讲了qwq,如果不了解的话可以去康康别的博客。
AC自动机建好之后,我们将所有的串在这上面跑,能走到打标记的点的串就是可以匹配的串。但是虽然一个匹配串长度只有不到50,但是数量太多了!我们要统计所有属于 ([x,y]) 的数字串,所以我们还得套一个数位dp。
定义 (f[i][j][0/1][0/1]) 表示枚举到数字第 (i) 位(从高位开始),现在在AC自动机上的哪个位置,是否卡上界,是否已经匹配,的方案数。最后统计答案就是将所有枚举到第 (d) 位的,在任意位置的,卡不卡上界都行的,已经匹配了的,方案数加起来。
跑两次数位dp记得清空数组,初始状态为 (f[0][0][1][0]=1) ,就是还没有放数字,在根节点,卡上界(一开始肯定要卡的,不然后面放飞了),没有匹配(没有空串肯定没有匹配)。
转移思路和其他的数位dp一样,这里AC自动机可以方便地进行位置上的转移,代码细节很多,要仔细一点,要么根本没法调。我们将转移按 “数字是否超上界或等于上界”,“是否已经匹配”来分成六类,每类分别进行不同的转移,调试可能也好调一些吧。
放代码的话我把模数去掉了,不然有点乱。
#include <bits/stdc++.h>
#define ll long long
#define F f[i-1][j]
using namespace std;
const int N=101010;
const int p=1000000007;
int n,m,d;
int a[53];
char s[N],z1[53],z2[53];
int ch[N][10],ed[N],fa[N],cnt,now;
ll f[53][25678][2][2];
vector <int> e[N];
queue <int> q;
void DFS(int u,bool flag) {
if(ed[u]) flag = 1;
if(flag) ed[u] = 1;
for(int i=0;i<e[u].size();i++) DFS(e[u][i],flag);
}
void AC() {
n = strlen(s+1);
for(int i=1;i+d/2-1<=n;i++) {
now = 0;
for(int j=i;j<=i+d/2-1;j++) {
int c = s[j] - '0';
if(!ch[now][c]) ch[now][c] = ++cnt;
now = ch[now][c];
}
ed[now] = 1;
}
for(int i=0;i<10;i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i=0;i<10;i++) {
int v = ch[u][i];
if(v) {
fa[v] = ch[ fa[u] ][i];
q.push(v);
}
else ch[u][i] = ch[ fa[u] ][i];
}
}
for(int i=1;i<=cnt;i++) e[ fa[i] ].push_back(i);
DFS(0,0);
}
int query() {
ll ans = 0;
memset(f,0,sizeof(f));
f[0][0][1][0] = 1;
for(int i=1;i<=d;i++) {
for(int j=0;j<=cnt;j++) {
for(int k=0;k<10;k++) {
int v = ch[j][k];
if(k<a[i]) {
if(ed[v]) {
f[i][v][0][1] += F[1][1] + F[0][1] + F[1][0] + F[0][0];
}
else {
f[i][v][0][1] += F[1][1] + F[0][1];
f[i][v][0][0] += F[1][0] + F[0][0];
}
}
if(k==a[i]) {
if(ed[v]) {
f[i][v][1][1] += F[1][1] + F[1][0];
f[i][v][0][1] += F[0][1] + F[0][0];
}
else {
f[i][v][1][1] += F[1][1];
f[i][v][1][0] += F[1][0];
f[i][v][0][1] += F[0][1];
f[i][v][0][0] += F[0][0];
}
}
if(k>a[i]) {
if(ed[v]) {
f[i][v][0][1] += F[0][1] + F[0][0];
}
else {
f[i][v][0][1] += F[0][1];
f[i][v][0][0] += F[0][0];
}
}
}
}
}
for(int i=0;i<=cnt;i++) ans += f[d][i][1][1] + f[d][i][0][1];
return ans;
}
int main() {
scanf("%s",s+1);
scanf("%s%s",z1+1,z2+1); d = strlen(z1+1);
AC();
for(int i=1;i<=d;i++) a[i] = z1[i] - '0'; int h = d; while(a[h]==0) a[h] = 9, h--; a[h]--;
ll ans1 = query();
for(int i=1;i<=d;i++) a[i] = z2[i] - '0';
ll ans2 = query();
cout<<((ans2-ans1)%p+p)%p;
return 0;
}
(2020.5.19) (update):
傻了傻了,为啥要定义的这么麻烦啊,我们只记录不经过任何标记点的情况数,再用总情况减去它不就行了?
少了一维,而且状态转移少了好几倍。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define QWQ cout<<"QwQ"<<endl;
#define ll long long
#include <vector>
#include <queue>
#include <stack>
#include <map>
#define F f[i-1][j]
using namespace std;
const int N=101010;
const int qwq=303030;
const int inf=0x3f3f3f3f;
int n,m,d;
int a[53];
char s[N],z1[53],z2[53];
int ch[N][10],ed[N],fa[N],cnt,now;
ll f[53][25678][2];
vector <int> e[N];
queue <int> q;
inline int read() {
int sum = 0, f = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') f = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * f;
}
void DFS(int u,bool flag) {
if(ed[u]) flag = 1;
if(flag) ed[u] = 1;
for(int i=0;i<e[u].size();i++) DFS(e[u][i],flag);
}
void AC() {
n = strlen(s+1);
for(int i=1;i+d/2-1<=n;i++) {
now = 0;
for(int j=i;j<=i+d/2-1;j++) {
int c = s[j] - '0';
if(!ch[now][c]) ch[now][c] = ++cnt;
now = ch[now][c];
}
ed[now] = 1;
}
for(int i=0;i<10;i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i=0;i<10;i++) {
int v = ch[u][i];
if(v) {
fa[v] = ch[ fa[u] ][i];
q.push(v);
}
else ch[u][i] = ch[ fa[u] ][i];
}
}
for(int i=1;i<=cnt;i++) e[ fa[i] ].push_back(i);
DFS(0,0);
}
int query() {
ll ans = 0;
memset(f,0,sizeof(f));
f[0][0][1] = 1;
for(int i=1;i<=d;i++) {
for(int j=0;j<=cnt;j++) {
for(int k=0;k<10;k++) {
int v = ch[j][k]; if(ed[v]) continue;
if(k<a[i]) f[i][v][0] += F[0] + F[1];
if(k>a[i]) f[i][v][0] += F[0];
if(k==a[i]) {
f[i][v][0] += F[0];
f[i][v][1] += F[1];
}
}
}
}
for(int i=0;i<=cnt;i++) ans += f[d][i][1] + f[d][i][0];
return ans;
}
int main() {
scanf("%s",s+1);
scanf("%s%s",z1+1,z2+1); d = strlen(z1+1);
AC();
ll shu1 = 0, shu2 = 0;
for(int i=1;i<=d;i++) a[i] = z1[i] - '0', shu1 = shu1 * 10 + a[i];
int h = d; while(a[h]==0) a[h] = 9, h--; a[h]--; shu1--;
ll ans1 = query();
for(int i=1;i<=d;i++) a[i] = z2[i] - '0', shu2 = shu2 * 10 + a[i];
ll ans2 = query();
cout<<(shu2-shu1) - (ans2-ans1);
return 0;
}
已略去模数。