1. 程式人生 > >bzoj 4870: [Shoi2017]組合數問題 動態規劃

bzoj 4870: [Shoi2017]組合數問題 動態規劃

題意

這裡寫圖片描述
1 ≤ n ≤ 10^9, 0 ≤ r < k ≤ 50, 2 ≤ p ≤ 2^30 − 1

分析

拿到題目就開始狂推式子,看了題解才發現原來是dp。
我們從直觀上來理解我們要求的這個詭異的式子。
實際上就是要我們從nk件物品裡面選出若干件,使得其數量模k等於r的方案數。
顯然的dp方程f[i,j]表示前i件物品拿了若干件使得其數量模k等於j的方案數。
那麼顯然有f[i,j]=f[i1,j]+f[i1,j1]
矩陣乘法優化即可。
複雜度O(k3logn)

還有一種更棒的做法,同樣是dp,但可以發現f[n2,i+j]+=f[n,i]f[n,j]
可以理解成列舉前n個物品的選法和後n個物品的選法。
那麼直接對dp陣列做快速冪即可。
複雜度O

(k2logn)

程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=55;

LL n,p,k,r;
struct arr{LL f[N];}ans,a;

void mul(arr &a,arr b,arr c)
{
    memset(a.f,0,sizeof(a.f));
    for
(int i=0;i<k;i++) for (int j=0;j<k;j++) a.f[(i+j)%k]=(a.f[(i+j)%k]+b.f[i]*c.f[j]%p)%p; } void ksm(arr x,LL y) { y--; while (y) { if (y&1) mul(ans,ans,x); mul(x,x,x);y>>=1; } } int main() { scanf("%lld%lld%lld%lld",&n,&p,&k,&r); a.f[0
]++;a.f[1%k]++;ans=a; ksm(a,n*k); printf("%lld",ans.f[r]); return 0; }