• [BZOJ4870][Shoi2017]组合数问题 dp+矩阵乘


    4870: [Shoi2017]组合数问题

    Time Limit: 10 Sec  Memory Limit: 512 MB

    Description

    Input

    第一行有四个整数 n, p, k, r,所有整数含义见问题描述。
    1 ≤ n ≤ 10^9, 0 ≤ r < k ≤ 50, 2 ≤ p ≤ 2^30 − 1

    Output

    一行一个整数代表答案。

    Sample Input

    2 10007 2 0

    Sample Output

    8
     
    题解:
    今年的省选题……
    题目的要求很简单,就是求满足要求的组合数在膜(?)意义下的和,但这其实是一道假的数学题。
    不难发现,对于符合要求的C(n*k)x中的x都有x%k==r
    我们考虑组合数最原始的应用:在一堆物品里选一些物品出来,那么题目的含义就是在n*k个物品中选x个物品,使x%k=r,求方案数
    这不就是个背包吗?
    那么我们设dp数组f[i][j]为前i个物品选出一些物品,使得物品的个数x%k==j
    那么不难写出转移方程:f[i][j]=f[i-1][j]+f[i-1][(j-1+k)%k](前者表示不选第i个,后者表示选)
    这显然是一个线性的递推式,因此我们考虑矩阵乘优化
    设矩阵A中A[i][j]表示从%k=i转移到%k=j的方案数
    那么我们的目标就是pow(A,n)之后的A[0][r];
    初始化的时候,把所有的A[j][j]++,所有A[(j-1+k)%k][j]++(类比上面的转移方程)
    然后再来一个单位矩阵一乘就好了。代码见下:
     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #include <cmath>
     5 using namespace std;
     6 typedef long long LL;
     7 const int N=60;
     8 LL n,p,k,r;
     9 struct marx
    10 {
    11     LL m[N][N];
    12     inline void print()
    13     {
    14         for(int i=0;i<k;i++)
    15         {
    16             for(int j=0;j<k;j++)
    17                 printf("%lld ",m[i][j]);
    18             printf("
    ");
    19         }
    20         printf("
    ");
    21     }
    22     inline void clear(){memset(m,0,sizeof(m));}
    23     marx operator * (const marx &b) const
    24     {
    25         marx c;c.clear();
    26         for(int i=0;i<k;i++)
    27             for(int j=0;j<k;j++)
    28                 for(int u=0;u<k;u++)
    29                     c.m[i][j]=(c.m[i][j]+m[i][u]*b.m[u][j])%p;
    30         return c;
    31     }
    32 }A,B,C;
    33 int main()
    34 {
    35     scanf("%lld%lld%lld%lld",&n,&p,&k,&r);
    36     A.clear(),B.clear(),C.clear();
    37     C.m[0][0]=1;
    38     for(int j=0;j<k;j++)
    39         B.m[j][j]=1,A.m[(j-1+k)%k][j]++,A.m[j][j]++;
    40     LL tmp=n*k;
    41     while(tmp)
    42     {
    43         if(tmp&1)B=B*A;
    44         tmp>>=1;A=A*A;
    45     }
    46     C=C*B;
    47     printf("%lld
    ",C.m[0][r]);
    48 }
  • 相关阅读:
    oracle 锁表查看与解锁
    oracle 监视索引是否使用
    oracle 查看数据库版本
    mybatis 中文做参数报错
    windows mysql绿色版配置
    maven使用本地jar包
    Java 使用Jedis连接Redis数据库(-)
    Linux 安装redis
    ELK之在CentOS7.5上使用rpm包安装配置ELK7版本
    Ubuntu-18.04更改安装源为国内源
  • 原文地址:https://www.cnblogs.com/LadyLex/p/7237421.html
Copyright © 2020-2023  润新知