[CERC2015] Digit Division 题解

O(n2) 做法

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

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

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

dpi=j=1i1dpj×flagj+1,i

那么答案就是在 dpn 这里了。

此做法时间复杂度为 O(n2),而 n3×105,并且无法进行优化,所以 DP 只能进行骗分。

O(n) 做法

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

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

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

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

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

一定要注意,如果整个字符串组成的数字不能被 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 @   Supor__Shoop  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Document
点击右上角即可分享
微信分享提示