炒鸡强的dp啊,原来组合题还可以这么做,用组合的意义来dp,只做过用组合优化dp。
我们考虑一下那个式子的意义:
nk个东西中取r个的方案数+nk个东西取k+r个的方案数+nk个东西取2k+r个的方案数+...
然后进一步转化问题:从nk个物品里选取膜k余r个物品的方案数。
那么怎么求这个问题呢?
很容易能想到一个简单的dp,设f[i][j]表示前i个物品取了膜k余j的方案数,转移很简单,f[i][j]=f[i-1][j]+f[i-1][(j+k-1)%k],一个取i,一个不取i。
然后我们发现这个式子只与f[i-1]有关,那么直接上矩阵快速幂。
代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
#define ll long long
using namespace std;
const int Maxn=55;
int p,k,r;
ll n;
struct _Matrix{ll Matrix[Maxn][Maxn];_Matrix(){for(int i=0;i<=53;i++)for(int j=0;j<=53;j++)Matrix[i][j]=0;}}f,m;
_Matrix operator * (_Matrix a,_Matrix b)
{
_Matrix c;
for(int i=0;i<k;i++)
for(int j=0;j<k;j++)
for(int l=0;l<k;l++)
c.Matrix[i][j]=(c.Matrix[i][j]%p+(a.Matrix[i][l]%p*b.Matrix[l][j]%p)%p)%p;
return c;
}
_Matrix operator ^ (_Matrix a,ll b)
{
_Matrix c;
for(int i=0;i<k;i++)
c.Matrix[i][i]=1;
while(b)
{
if(b&1)c=c*a;
a=a*a;b>>=1;
}
return c;
}
int main()
{
scanf("%lld%d%d%d",&n,&p,&k,&r);
for(int i=0;i<k;i++)
m.Matrix[(i-1+k)%k][i]++,m.Matrix[i][i]++;
n*=k;
m=m^n;
f.Matrix[0][0]=1;
f=f*m;
printf("%lld",f.Matrix[0][r]%p);
return 0;
}