BZOJ-2326-数学作业-HNOI2011-矩阵乘法

描述



分析

  • 如果用 f[i] 表示 i 时 Concatenate(1 .. i) Mod M 的值, 如果 i 是个 k 位数, 则 f[i+1] = f[i] * (10^k) + i+1, (i != 10^k-1)
  • 所以可以建立一个按 i 的位数分段的动态规划解法 -> f[n]
  • n ≤ 10^18, 所以要用矩阵乘法优化
  • 然后就是矩阵的选取了, 我首先考虑的 2×2 的矩阵能不能解决, 发现不能于是看了一下 HZWER 开的数组大小是4, 所以应该是 3×3 的矩阵了.
  • 之所以 2×2 的友矩阵不能解决是因为 如果把矩阵 {f[i], i} 设为求解的矩阵, 那么无论乘哪个 2×2 的友矩阵都不能在转移到 f[i+1] 时使第二列的 i 也加1
  • 那么用 3×3 的友矩阵, 求解的矩阵 {f[i], i, x}. 这个 i 要想每次加1, 只能依靠这个 x 乘上友矩阵的 a[2][2] (从0开始的下标) 所代表的数. 所以把x和a[2][2]都设置成1就可以使i每次加1了.
  • 关于矩阵的其他元素
  • 我的思路是第一次求 f[0], 乘10次之后得到 f[9], 然后进入下一层. 根据转移的方程可以得到友矩阵是 {{k,0,0},{1,1,0},{0,1,1}},其中k是方程中的 10^k. 这样每次乘完之后得到的是 {f[i+1], i+2, 1}. 然后我看HZWER的思路中的友矩阵是{{k,0,0},{1,1,0},{1,1,1}},这样每次得到的是 {f[i+1], i+1, 1}.
  • 其实两种思路都可以得到最终解.
  • 当转移到下一阶段时, k要×10, 之后友矩阵自乘的次数是 k-k/10. 因为举个例子从 f[10^m-1]转移到f[10^(m+1)-1]实际上转移了10^m-10^(m-1)次.
  • 但是按照我的思路, 在k=10的时候 10-10/10 = 9, 但实际上要转移10次, 所以程序开始要先单独处理k=10, 然后循环 k = 100 to ..
  • 当 k > n 之后还需要继续转移的次数是 n-k/10+1 次.
  • long long 和 快速乘 都是需要的.

代码

#include 
   
   
    
    
using namespace std;
typedef long long lli;

lli mod;

lli multi(lli a, lli b) {
	lli ret = 0;
	for(; b; b>>=1, a=(a<<1)%mod) if(b&1) ret = (ret+a) % mod;
	return ret;
}

struct Matrix {
	lli a[3][3];
	
	Matrix operator ^ (lli p) {
		Matrix t = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}, A = *this;
		for(; p; p>>=1, A=A*A) if(p & 1) t = t*A;
		return t;
	}
	
	Matrix operator * (Matrix A) {
		Matrix ret;
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++) {
				ret.a[i][j] = 0;
				for(int k = 0; k < 3; k++)
					ret.a[i][j] = (multi(a[i][k], A.a[k][j]) + ret.a[i][j]) % mod;
			}
		return ret;
	}
};

int main()
{
	lli n, k = 10;
	scanf("%lld %lld", &n, &mod);
	Matrix F = {{0, 0, 1}};
	Matrix A = {{{k, 0, 0}, {1, 1, 0}, {0, 1, 1}}};
	if(n >= 10) F = F*(A^10);
	for(k = 100; ; k*=10) {
		Matrix A = {{{k, 0, 0}, {1, 1, 0}, {0, 1, 1}}};
		if(n >= k) F = F * (A^(k-k/10)); else { F = F * (A^(n-k/10+1)); break; }
	}
	printf("%lld\n", F.a[0][0]);
	return 0;
}
#include 
    
    
     
     
using namespace std;
typedef long long lli;

lli mod;

lli multi(lli a, lli b) {
	lli ret = 0;
	for(; b; b>>=1, a=(a<<1)%mod) if(b&1) ret = (ret+a) % mod;
	return ret;
}

struct Matrix {
	lli a[3][3];
	
	Matrix operator ^ (lli p) {
		Matrix t = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}, A = *this;
		for(; p; p>>=1, A=A*A) if(p & 1) t = t*A;
		return t;
	}
	
	Matrix operator * (Matrix A) {
		Matrix ret;
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++) {
				ret.a[i][j] = 0;
				for(int k = 0; k < 3; k++)
					ret.a[i][j] = (multi(a[i][k], A.a[k][j]) + ret.a[i][j]) % mod;
			}
		return ret;
	}
};

int main()
{
	lli n;
	scanf("%lld %lld", &n, &mod);
	Matrix F = {{0, 0, 1}};
	for(lli k = 10; ; k*=10) {
		Matrix A = {{{k, 0, 0}, {1, 1, 0}, {1, 1, 1}}};
		if(n >= k) F = F * (A^(k-k/10)); else { F = F * (A^(n-k/10+1)); break; }
	}
	printf("%d\n", F.a[0][0]);
	return 0;
}

    
    
   
   


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值