Codeforces Round #341 (Div. 2) E. Wet Shark and Blocks(DP+矩阵快速幂)

3 篇文章 0 订阅

题目链接: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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值