题目链接:Problem - 621E - Codeforces
一共b位数字,给出1~9出现的次数,从1~9中选,求模x等于b的数字的个数
DP[i][j]表示i位数字,模x等于j的数字个数,状态转移方程是dp[k][(i*10+j)%x] += dp[k-1][i]*cnt[j]。
观察可以发现dp[k][]的状态只与dp[k-1][]有关,所以直接递推就可以得到正确答案。但是这道题目的b有1e9大,直接递推会挂。
线性递推可以用矩阵快速幂来优化,将时间复杂度从O(n)降到O(logn),所以我们可以计算出递推矩阵A,直接A^n左乘初始矩阵B(B是一个x行1列的矩阵,B[i][0]表示模x等于i的个数,同时B[0][0]=1,表示数字长度为0时,模x等于0的个数为1,其余均为0)就可以得到最终状态。代码中left为A矩阵,right为B矩阵。
#include <set>
#include <map>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
#define FIN freopen("in.txt", "r", stdin);
#define FOUT freopen("out.txt", "w", stdout);
#define lson l, mid, cur << 1
#define rson mid + 1, r, cur << 1 | 1
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 50;
const int MOD = 1e9 + 7;
int n, b, k, x, cnt[11];
typedef vector<int> vec;
typedef vector<vec> mat; // mat var(n, vec(m) n行m列矩阵
mat mat_mul(mat &A, mat &B)
{
mat C(A.size(), vec(B[0].size()));
for(int i = 0; i < A.size(); i++)
for(int j = 0; j < B[0].size(); j++)
for(int k = 0; k < B.size(); k++)
C[i][j] = ((LL)A[i][k] * B[k][j] + C[i][j]) % MOD;
return C;
}
mat mat_pow(mat A, LL n)
{
mat B(A.size(), vec(A.size()));
for(int i = 0; i < A.size(); i++)
B[i][i] = 1;
while(n)
{
if(n & 1)
B = mat_mul(B, A);
A = mat_mul(A, A);
n >>= 1;
}
return B;
}
int main()
{
#ifndef ONLINE_JUDGE
FIN;
#endif // ONLINE_JUDGE
while (~scanf("%d%d%d%d", &n, &b, &k, &x))
{
memset(cnt, 0, sizeof(cnt));
while (n--)
{
int t;
scanf("%d", &t);
cnt[t]++;
}
mat left(x, vec(x)), right(x, vec(1));
for (int i = 0; i < x; i++)
for (int j = 1; j <= 9; j++)
left[(i * 10 + j) % x][i] += cnt[j];
right[0][0] = 1;
left = mat_pow(left, b);
right = mat_mul(left, right);
printf("%d\n", right[k][0] % MOD);
}
return 0;
}