[CERC2015] Digit Division 题解

\(O(n^2)\) 做法

和大部分人最开始一样,我也想的是 DP。

\(dp_i\) 表示用前面 \(i\) 个字符拆分得到的答案。既然是统计方案数,我们肯定是根据前面的答案累加。考虑在 \([1,i-1]\) 中选择一个 \(j\),如果 \([j+1,i]\) 的字符组成的数字能够被 \(m\) 整除,那么 \(dp_i\) 就可以累加一个 \(dp_j\) 的值,因为如果当前区间满足条件,就相当于这里是一个可行的拆分,那么前面 \(j\) 个字符得到的答案很明显也都可以成为累加的一部分。

假设 \(flag_{j,i}\) 表示区间 \([j,i]\) 组成的数字是否可以被 \(m\) 整除,\(1\) 表示可以,\(0\) 表示不可以。则有转移方程:

\[dp_i= \sum _{j=1}^{i-1}dp_j \times flag_{j+1,i}\]

那么答案就是在 \(dp_{n}\) 这里了。

此做法时间复杂度为 \(O(n^2)\),而 \(n \leq 3\times 10^5\),并且无法进行优化,所以 DP 只能进行骗分。

\(O(n)\) 做法

考虑运用数学运算进行求解。

设想一下,假如字符串的前缀组成的数字 \(x\) 能够被 \(m\) 整除会怎么样?如果整个字符串组成的数字 \(sum\) 也能够被 \(m\) 整除,那么这个前缀以后的所有字符组成的数字也必定可以被 \(m\) 整除。即 \(m\mid sum-x\times 10^{num}\),其中 \(num\)非前缀的字符个数。这个是非常容易想到的一个式子。

那么这样的一个式子有什么用呢?既然前缀后面的数字可以被 \(m\) 整除,那么我们能否按照相同的思路,在这之中进行拆分?假设后面的数字为 \(sum\),在这个数字里面找一个前缀组成数字 \(x\),由上文第一步推断知道 \(m\mid sum\),如果此时 \(m\mid x\),那么这个前缀后面的数字也可以被 \(m\) 整除,这个思路和上文一模一样。

所以我们可以得出一个结论,如果整个字符串的某个前缀组成的数字能被 \(m\) 整除,且整个字符串组成的数字能够被 \(m\) 整除,那么此时这个前缀的最后一个字符的下标处就是一个可以进行拆分的地方。如果在这里进行拆分,那么前后的字符串也都会被 \(m\) 整除,因此这里一定会被某一个拆分方式进行拆分。

所以我们可以找到所有被 \(m\) 整除的前缀数字,记录下这样的前缀的个数 \(res\)。然后这个问题就转化成了对 \(res\) 个可以拆分的地方进行组合。因为这里面的前缀会包括整个字符串,所以中间选择的拆分的地方有 \(res-1\) 个。由于每个地方有选与不选\(2\) 种可能,因此计算的答案就是 \(2^{res-1}\) 次方。而求幂我们使用快速幂就可以了。

一定要注意,如果整个字符串组成的数字不能被 \(m\) 整除,那么答案一定\(0\),因为找不到任何一个拆分的地方,使得前后两个数字都能够被 \(m\) 整除。

代码如下:

#include<bits/stdc++.h>
#define int long long//方案取模题,日常开 long long 
using namespace std;
const int MAXN=3e5+5;
const int MOD=1e9+7;
int n,m;
char s[MAXN];
int quick_pow(int x)//2^x的快速幂 
{
	int ans=1,sum=2;
	while(x)
	{
		if(x&1)	ans=ans*sum%MOD;
		sum=sum*sum%MOD;
		x>>=1;
	}
	return ans;
}
signed main()
{
	cin>>n>>m>>(s+1);
	int res=0,x=0;
	for(int i=1;i<=n;i++)	x=(x<<1)+(x<<3)+(s[i]^48),x%=m,res+=(!x);//计算可拆分地方的个数 
	if(x)	puts("0");//特判 
	else	cout<<quick_pow(res-1);//组合 
	return 0;
}
posted @ 2024-09-27 12:12  Supor__Shoop  阅读(5)  评论(0编辑  收藏  举报
Document