HDU5564 – Clarke and digits(矩阵乘法优化DP)

HDU5564

【题意】

克拉克是一名人格分裂患者。某一天,克拉克变成了一个研究人员,在研究数字。
他想知道在所有长度在[l, r]之间的能被7整除且相邻数位之和不为k的正整数有多少个。l,r <= 10^9; 结果模1e9+7

【分析】首先想到的DP的转移,dp[i][j][k]表示长度为i位的数最低位为j且值%7=k,那么dp[i+1][l][(k*10+l)%7] += dp[i][j][k] (0 <= l <= 9);(从高位往低位推)。

但是位数有10^9直接递推肯定不行。然后发现后面两维的状态转移始终固定的,那么把状态压缩成一维后只有70种,就可以用70*70矩阵x来表示状态i能否转移到状态j,这样预处理dp[1]后,dp[1] * x^(n-1)就是dp[n]的结果了。这样就和POJ3734类似了。

好像完成了?还有一步,我们要求的是区间[l,r]之间有多少个数,而dp[i]表示的是长度为i位的数的方案数,所以需要增加一维来表示前缀和,这里我增加了一维表示dp[i-1]能被7整数之和。总复杂度71^3*log(r)具体看代码注释吧。

【AC CODE】1138ms

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f, MAXN = 71;
LL MOD = 1e9+7;
struct MATRIX{
	LL num[MAXN][MAXN];
	MATRIX(){
		clc(num,0);
	}
	MATRIX operator*(const MATRIX& b)const{
		MATRIX ans;
		rep(i,0,MAXN)
		{
			rep(k,0,MAXN)
			{
				rep(j,0,MAXN)
				{
					ans.num[i][j] = (ans.num[i][j]+num[i][k]*b.num[k][j])%MOD;
				}
			}
		}
		return ans;
	}
	MATRIX operator ^(int n)
	{
		MATRIX ans, x = *this;
		if(n < 0) return ans;
		rep(i,0,MAXN) ans.num[i][i] = 1;
		while(n)
		{
			if(n&1) ans = ans*x;
			x = x*x;
			n >>= 1;
		}
		return ans;
	}
};
LL st[MAXN],ed[MAXN];//初始状态和最后状态的各个结果,0~69就是对应dp的70个状态,70对应dp[i-1]所有%7=0的方案之和

inline int mhash(int x, int y){return x*7+y;}//mhash(x,y)表示把最低位为x且%7为y的状态
int main()
{
#ifdef SHY
	freopen("d:\\1.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	rep(i,1,10) st[mhash(i,i%7)] = 1;//dp[1][i][i%7]=1;
	while(t--)
	{
		int l,r,m;
		scanf("%d%d%d", &l,&r,&m);
		//构造转移矩阵x
		MATRIX x;
		rep(j,0,10)
		{
			rep(l,0,10)
			{
				if(j+l == m) continue;
				rep(k,0,7)
				{
					x.num[mhash(j,k)][mhash(l,(k*10+l)%7)] = 1;
					//dp[i+1][l][(k*10+l)%7] += dp[i][j][k];
				}
			}
		}
		rep(j,0,10) x.num[mhash(j,0)][70] = 1;//累加dp[i-1]中%7=0的所有状态
		x.num[70][70] = 1;//x*x过程中始终是1,是为了最后乘上st矩阵时ed[70]不为0
		MATRIX ret = x^(r-1);
		//初始矩阵乘上x^(r-1)则就是dp[r]的所有结果
		clc(ed,0);
		rep(i,0,MAXN)
		{
			rep(j,0,MAXN)
				ed[i] = (ed[i]+st[j]*ret.num[j][i])%MOD;
		}
		LL ans = ed[70];//sum{dp[1 ~ i-1];}
		rep(j,0,10) ans = (ans+ed[mhash(j,0)])%MOD;//加上当前dp[r]中所有方案数
		//初始矩阵乘上x^(l-2)则就是dp[l-1]的所有结果
		ret = x^(l-2);
		clc(ed,0);
		rep(i,0,MAXN)
		{
			rep(j,0,MAXN)
				ed[i] = (ed[i]+st[j]*ret.num[j][i])%MOD;
		}
		LL ans2 = ed[70];
		rep(j,0,10) ans2 = (ans2+ed[mhash(j,0)])%MOD;
		printf("%lld\n", (ans-ans2+MOD)%MOD);
	}
	return 0;
}

 

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

请输入正确的验证码