标签:++ def 质量 开始 适合 for 现在 code query
给定长度为 \(n\) 的数字串 \(s\) 和长度为 \(d\) 的不含前导零的数字串 \(x,y(x \le y)\)。
求存在长度至少为 \(\left\lfloor\frac{d}{2}\right\rfloor\) 的子串是 \(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;
}
标签:++ def 质量 开始 适合 for 现在 code query
原文地址:https://www.cnblogs.com/clever-sheep/p/12887764.html