P10262 [GESP样题 六级] 亲朋数

题目大意:

对于一个数字串 \(l\) (允许前导零),其连续子串的数量为 \(\frac{l(l+1)}{2}\) .现在给定一个整数 \(p\) ,如果 \(l\)
某一个连续字串为 \(p\) 倍数,那么我们称这个数为l对于p的亲朋数

当然,也有可能连续子串中会有多个亲朋数,那么现在给定 \(l\)\(p\) ,求有多少个亲朋数?

数据范围:\(1 \le p \le 128\) ,数字串 \(l\) 的长度 \(s\) 满足 \(1 \le s \le 10^6\).

解法:

本解法是对这篇博客的一个再讲解.

说到倍数,自然也有余数的数,那么,既然连续子串取余会有多种结果,那么每种情况是否可以转移呢?

定义 \(dp_{i,j}\) 为当前以 \(l_i\) 为子串结尾,取余 \(p\) 的结果为 \(j\) 的子串个数.
由于最后我们只需要求子串的数量,而判断一个数是否是 \(p\) 的倍数,在取余之前对各个位再次取余,由于同余定理,所以保证最后取余 \(p\) 的结果仍然不会变.

现在,假设上一位为 \(j\) ,这一位为 \(q\) ,那么在加上这一位后的连续子串应该为 \(10 * j + q\) ,但是因为有同余定理,所以我们可以自然而然的取余,把第二维限制在p的范围,毕竟 \(p\) 不大.

那么既然能接上一位,自然也可以不接,而不接单单一个数,也是一个字串,所以也要统计.

还可以知道,由于每次方程都只是使用了上一位,而前面的计算结果后续将不再使用,为了节省空间,我们采用滚动数组的方法,
每次只记录当前位和上一位的结果,在切到下一位时,当前位则转到上一位的数组.

还有,一定要开long long!不开会全wa的!!!

#include<bits/stdc++.h> 
#define int long long
using namespace std;
string s;
int p,ans,f[150],dp[150];
signed main(){
	cin >> p >> s;
	for(int i = 0;i < s.size();i++){
		int q = s[i] - '0';
		q %= p;
		for(int j = 0;j < p;j++){
			f[j] = dp[j];
			dp[j] = 0;
		}
		for(int j = 0;j < p;j++)
			dp[(j * 10 + q) % p] += f[j];
		dp[q]++;
		ans += dp[0];
	}
	cout<<ans;
	return 0;
}

谢谢观看!

posted @ 2026-07-12 09:26  Cai_hy  阅读(41)  评论(0)    收藏  举报