2021-2022-1 ACM集训队每周程序设计竞赛(4) - 问题 D:等差数列求和: - 题解

题意
给定一个长度为 K K K ,首项为 A A A ,公差为 B B B 的等差数列 S S S,把这 K K K个数从小到大拼接到一起,形成一个新的数 N N N,求 N % M N\%M N%M的值。

思路
b i t ( i ) bit(i) bit(i) 表示第 i 项所需要进行的十进制位移。
那么根据题目所给的信息, N = S 0 ∗ 1 0 b i t ( 0 ) + S 1 ∗ 1 0 b i t ( 1 ) + ⋯ + S K − 1 ∗ 1 0 b i t ( K − 1 ) N=S_{0}∗10^{bit(0)}+S_{1}∗10^{bit(1)}+⋯+S_{K-1}∗10^{bit(K-1)} N=S010bit(0)+S110bit(1)++SK110bit(K1) ,这么大的式子,就算是 p y py py 暴力做也会超时。

然后,我们注意到,“等差数列中的所有项都小于 1 0 18 10^{18} 1018”,也就是说,等差序列中很多项的长度是相等的,而对于 b i t ( i ) bit(i) bit(i) 这个函数,有很多连续的项可以看成等差序列。于是我们可以按照位数给等差数列分组,最多可分 18 组。

接下来就具体看一个等差序列区间 [ L , R ] [L,R] [L,R] ,假定 每一项等差序列的长度均为 m m m
设这个区间上所表示的数为 F ( m ) F(m) F(m) F ( m ) F(m) F(m) 中的每一项 f i = S i ∗ 1 0 i ( L ≤ i ≤ R ) f_{i}=S_{i}*10^{i}(L\leq i\leq R) fi=Si10i(LiR),与之相对应的:
F ( m ) = ∑ i = L R f i = 1 0 b i t ( L ) ∗ ∑ i = L R ( S i ∗ 1 0 ( R − i ) ∗ m ) F(m)=\sum_{i=L}^Rf_{i}=10^{bit(L)}*\sum_{i=L}^R(S_{i}∗10^{(R-i)*m}) F(m)=i=LRfi=10bit(L)i=LR(Si10(Ri)m)
经过一系列的代换,现在需要被解决的就只有 ∑ i = L R ( S i ∗ 1 0 ( R − i ) ∗ m ) \sum_{i=L}^R(S_{i}∗10^{(R-i)*m}) i=LR(Si10(Ri)m) 了。

r e t ( j ) = ∑ i = L j ( S i ∗ 1 0 ( j − i ) ∗ m ) ( L ≤ j ≤ R ) ret(j)=\sum_{i=L}^j(S_{i}∗10^{(j-i)*m})(L\leq j\leq R) ret(j)=i=Lj(Si10(ji)m)(LjR),这玩意其实就是 r e t ( j + 1 ) = ∑ i = L j ( S i ∗ 1 0 ( j − i ) ∗ m ) ∗ 1 0 m + S j + 1 ret(j+1)=\sum_{i=L}^j(S_{i}∗10^{(j-i)*m})*10^{m}+S_{j+1} ret(j+1)=i=Lj(Si10(ji)m)10m+Sj+1
这一坨真要一步一步搞也是个麻烦事,不如……我们把它变个样子:
r e t ( L − 1 ) = 0 ret(L-1)=0 ret(L1)=0,构造如下系数矩阵 X X X 与矩阵 R E T ( j ) RET(j) RET(j)
X = [ 1 0 m 0 0 1 1 0 0 B 1 ] , R E T ( j ) = [ r e t ( j ) S j + 1 1 ] X= \begin{gathered} \begin{bmatrix} 10^{m} & 0 &0 \\ 1 & 1 & 0\\ 0 & B & 1 \end{bmatrix} \end{gathered}, RET(j)= \begin{gathered} \begin{bmatrix} ret(j) & S_{j+1} & 1\end{bmatrix} \end{gathered} X=10m1001B001RET(j)=[ret(j)Sj+11]
于是:
R E T ( j ) = R E T ( j − 1 ) ∗ X , R E T ( j ) = R E T ( L − 1 ) ∗ X j − L + 1 RET(j)=RET(j−1)∗X,RET(j)=RET(L−1)∗X^{j−L+1} RET(j)=RET(j1)XRET(j)=RET(L1)XjL+1
通过矩阵快速幂,算出长度为 m m m 的一组值,最后再将每一组的结果相加即可。

时间复杂度 O ( O( O不会算 ) )

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define VV vector<vector<int> >
#define init(x) vector<vector<int>> x(3, vector<int>(3, 0))
int K, A, B, mod, ans;

//inline void print(VV u) {
//	for (int i = 0; i < 3; i++) {
//		for (int j = 0; j < 3; j++) {
//			cout << u[i][j] << " ";
//		}
//		cout << endl;
//	}
//}

//矩阵乘法
VV mat_mul(VV a, VV b) {
	init(res);

	for (int k = 0; k < 3; k++)
		for (int i = 0; i < 3; i++) {
			if (a[i][k] == 0)
				continue;
			for (int j = 0; j < 3; j++)
				res[i][j] = (res[i][j] + a[i][k] * b[k][j] % mod) % mod;
		}

	return res;
}

//快速幂
VV mat_pow(VV cnt, int k) {
	init(res);
	res[0][0] = res[1][1] = res[2][2] = 1;

	while (k) {
		if (k & 1)
			res = mat_mul(res, cnt);
		cnt = mat_mul(cnt, cnt);
		k >>= 1;
	}

	return res;
}

// 从数列第 st 项开始,查找区间 [L, R],使得区间内的所有数都小于 bit 大于等于 bit/10
int st = 0, bit = 10, l, r;
bool get() {
	if (st >= K || A + st * B >= bit)
		return false;

	l = st;
	r = K - 1;

	while (l < r) {
		int mid = (l + r) >> 1;
		if (A + mid * B < bit)
			l = mid + 1;
		else
			r = mid;
	}//二分查找区间右端点

	if (A + r * B >= bit)
		--r;
	l = st;
	st = r + 1; // 下一个起始位置
	return true;
}

signed main() {
	cin >> K >> A >> B >> mod;
	//最多有18位
	for (int i = 1; i <= 18; i++) {
		if (get()) {

			init(mat);
			mat[0][0] = bit % mod;
			mat[0][1] = mat[0][2] = mat[1][2] = mat[2][1] = 0;
			mat[1][0] = mat[1][1] = mat[2][2] = 1;
			mat[2][1] = B % mod;

			mat = mat_pow(mat, r - l + 1);

			vector<int> ret(3), tem(3);
			ret[0] = ans;
			ret[1] = (A + l * B) % mod;
			ret[2] = 1;
			tem[0] = tem[1] = tem[2] = 0;

			for (int j = 0; j < 3; j++)
				for (int k = 0; k < 3; k++)
					tem[j] = (tem[j] + ret[k] * mat[k][j] % mod) % mod;

			ans = tem[0];
		}
		bit *= 10;
	}
	cout << ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值