hdu5564 Clarke and digits

传送门

矩阵优化 D P DP DP一篇讲解的DP博客

最开始想到 D P DP DP如下:令 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前为第 i i i位,余数为 j j j,最后一位为 k k k的方案。假设相邻两位之和不为 b a n ban ban
i f ( x + k ! = b a n ) = > d p [ i + 1 ] [ ( j ∗ 10 + x ) % 7 ] [ x ] + = d p [ i ] [ j ] [ k ] if(x+k!=ban)=>dp[i+1][(j*10+x)\%7][x]+=dp[i][j][k] if(x+k!=ban)=>dp[i+1][(j10+x)%7][x]+=dp[i][j][k]
最后的答案即为 ∑ l &lt; = i &lt; = r , 0 &lt; = k &lt; = 9 d p [ i ] [ 0 ] [ k ] \sum_{l&lt;=i&lt;=r,0&lt;=k&lt;=9}{dp[i][0][k]} l<=i<=r,0<=k<=9dp[i][0][k]
复杂度 O ( n ∗ 7 ∗ 10 ∗ 10 ) O(n*7*10*10) O(n71010)。然而这里的 n &lt; = 1 e 9 n&lt;=1e9 n<=1e9,不行啊。

观察到 [ j ] [ k ] [j][k] [j][k]这两维都很小。可以压缩成 [ j ∗ 10 + k ] [j*10+k] [j10+k]然后把上面的转化一下可以得到:
i f ( x + k ! = b a n ) = &gt; d p [ i + 1 ] [ (   ( j ∗ 10 + x ) % 7   ) ∗ 10 + x ] + = d p [ i ] [ j ∗ 10 + k ] if(x+k!=ban)=&gt;dp[i+1][(\ (j*10+x)\%7\ )*10+x]+=dp[i][j*10+k] if(x+k!=ban)=>dp[i+1][( (j10+x)%7 )10+x]+=dp[i][j10+k]
可以利用矩阵乘法来优化。因为有这三个特点

1.转移要选取的决策较少。(一般在常数级别)
2.转移的步骤很多。(一般是1e10以上的级别)
3.每一步的转移方程一样。(和递推类似)

问题可以先转成长度 [ 1 , n ] [1,n] [1,n]的个数。用 ( [ 1 , r ] − [ 1 , l − 1 ] ) ([1,r]-[1,l-1]) ([1,r][1,l1])就行了。
那么这里就要求一个前缀和。一个巧妙的解决办法:多开一格记录前缀和。转移矩阵里面多开一列(紫色部分),记录余数为0的累积和。
乘的时候是左边的一行乘右边对应的一列。记在一行的对应格。
在这里插入图片描述
横行对应为 x x x,纵列对应为 y y y,那么 B [ x ] [ y ] B[x][y] B[x][y]表示从 x x x转移到 y y y的方案数。
最右一列打勾的是记录当前长度满足条件的方案数要用,下面那个阴影的格子是记前缀和要用。因为是乘积相加,求和之后就把总和缩到实心的紫色格子了。也就是长度为 [ 1 , n ] [1,n] [1,n]的总方案数。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int maxn=71;
inline int id(int i,int j){return i*10+j;}
inline int mul(int a,int b){return (long long)a*b%mod;}
inline int add(int a,int b){return (a+b)>=mod ? (a+b)%mod : a+b;}
struct matrix{
	int a[maxn][maxn];
	matrix(int t=0){
		memset(a,0,sizeof(a));
		for(int i=0;i<maxn;++i) a[i][i]=t;
	}
	friend inline matrix operator*(const matrix &a,const matrix &b){
		matrix ret;
		for(int i=0;i<maxn;++i)
			for(int j=0;j<maxn;++j)
				for(int k=0;k<maxn;++k)
					ret.a[i][j]=add(ret.a[i][j],mul(a.a[i][k],b.a[k][j]));
		return ret;
	}
	friend inline matrix operator^(matrix a,int b){
		matrix ret(1);
		for(;b;a=a*a,b>>=1) if(b&1) ret=ret*a;
		return ret;
	}
};
int n,l,r,k;
int main(){
	cin>>n;
	while(n--){
		cin>>l>>r>>k;matrix A,B;
		for(int i=0;i<7;++i)
			for(int j=0;j<10;++j)
				for(int x=0;x<10;++x) if(j+x!=k)
					B.a[id(i,j)][id((i*10+x)%7,x)]++;
		for(int i=0;i<10;++i) B.a[id(0,i)][maxn-1]++;
		B.a[maxn-1][maxn-1]=1;
		for(int i=1;i<10;++i) A.a[0][id(i%7,i)]++;
		matrix a=A*(B^r),b=A*(B^(l-1));
		int ans=((a.a[0][maxn-1]-b.a[0][maxn-1])+mod)%mod;
		printf("%d\n",ans);
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值