hdu 5564 Clarke and digits (dp+矩阵加速)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5564

题意:

问题描述
克拉克是一名人格分裂患者。某一天,克拉克变成了一个研究人员,在研究数字。  
他想知道在所有长度在[l, r][l,r]之间的能被77整除且相邻数位之和不为kk的正整数有多少个。  
输入描述
第一行一个整数T(1 \le T \le 5)T(1T5),表示数据的组数。  
每组数据只有一行三个整数l, r, k(1 \le l \le r \le 10^9, 0 \le k \le 18)l,r,k(1lr109,0k18)
输出描述
每组数据输出一行一个数,表示答案。由于答案太大,你只需对10^9+7109+7取模即可。  
输入样例
2
1 2 5
2 3 5
输出样例
13
125
Hint
第一个样例有13个数满足,分别是:7,21,28,35,42,49,56,63,70,77,84,91,987,21,28,35,42,49,56,63,70,77,84,91,98

分析:
定义dp[i][j][k]表示位数为i,当前数%7的值而且以k结尾的方案数。
那么有:       
for(int i=1;i<r;i++)
for(int j=0;j<7;j++)
for(int k=0;k<10;k++)
for(int x=0;x<10;x++) if(k+x!=K)
dp[i+1][( j*10+x)%7][x]+=dp[i][j][k];
但是r<=1e9,很大。不能直接递推,需要矩阵快速幂来加速递推。
首先建状态转移图(初始矩阵),mat[s1][s2]表示从状态s1到状态s2的方案数,当前数%7的值为i且以j结尾,s1= i*10+j,添加一个数x,i变为i' ,i'=( i*10+x)%7,j变为j',j'=x,那么状态s2=i'*10+j'。
然后需要多出一列来算累加和。
最后用快速幂直接求解。

代码:
#include <bits/stdc++.h>
using namespace std;

const int maxn = 72;
const int mod = 1e9+7;
struct Matrix
{
	int mat[maxn][maxn];
	Matrix()
	{memset(mat,0,sizeof(mat));}
	friend Matrix operator *(const Matrix &A,const Matrix &B);
	friend Matrix operator ^(Matrix A,int n);
};

Matrix operator *(const Matrix &A,const Matrix &B) //mul 
{
	Matrix ret;
	for(int i=0;i<maxn;i++)
		for(int j=0;j<maxn;j++)
			for(int k=0;k<maxn;k++)
				(ret.mat[i][j]+=1ll*A.mat[i][k]*B.mat[k][j]%mod)%=mod;
	return ret;
}
Matrix operator ^(Matrix A,int n)  //pow
{
	Matrix ret;
	for(int i=0;i<maxn;i++)
		ret.mat[i][i]=1;
	for(;n;n>>=1,A=A*A) if(n&1)
		ret=ret*A;
	return ret;
}
inline int Statu(int i,int j)
{
	return i*10+j;
}
int main()
{
	int nCase,i,j,L,R,K;
	scanf("%d",&nCase);
	while(nCase--)
	{
		scanf("%d%d%d",&L,&R,&K);
		Matrix A,B;
		for(i=0;i<7;i++)
			for(j=0;j<10;j++)
				for(int x=0;x<10;x++) if(j+x!=K)
					B.mat[Statu(i,j)][Statu((i*10+x)%7,x)]++;
		for(i=0;i<10;i++)
			B.mat[Statu(0,i)][maxn-1]++;
		B.mat[maxn-1][maxn-1]=1;
		for(i=1;i<10;i++)
			A.mat[0][Statu(i%7,i)]++;
		Matrix ret1=A*(B^R);
		Matrix ret2=A*(B^(L-1));
		int res=ret1.mat[0][maxn-1]-ret2.mat[0][maxn-1];
		while(res<0)	res+=mod;
		while(res>=mod)	res-=mod;
		printf("%d\n",res);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值