CF585F Digits of Number Pi

题意:

codeforces链接

给定长度为 n 的数字串 s 和长度为 d 的不含前导零的数字串 x,y(xy)

存在长度至少为 d2 的子串是 s 的子串的数字串 t[x,y] 的数量。

n103d50,答案对 109+7 取模。


算一道挺难的题,质量也不错,适合练码力。

怎么表示一个长度为 d2 的串在s中出现,我们发现s长度并不是很大,1000,我们把s中所有长度大于等于 d2 的串一起建立一个AC自动机,然后发现只需要把长度等于 d2 的建AC自动机就好了,因为包含长度大于 d2 的串也就肯定包含等于 d2 的串。

然后把每个串的末尾点和该点fail子树上所有点打上标记,表示如果走到这个点,就能包含一个长度为 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; }

已略去模数。


__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/12887764.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(153)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示