CF585F Digits of Number Pi

题意:

codeforces链接

给定长度为 \(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;
}

\(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;
}

已略去模数。

posted @ 2020-05-14 12:15  maple276  阅读(153)  评论(0编辑  收藏  举报