参考题解http://blog.csdn.net/largecub233/article/details/73457992
本题中的m,k比较小,因此可以用来状压,共有n朵花,设到第i朵花时候的状态为f[i][j],j为以i为结束点的的最近m个花的情形。那么当前的j状态可以由哪些k转移到呢,j左移一位后补0或者1。我们可以提前枚举转移的状态,构造一个状态转移图v[j][k]。
那么,if(v[j][k])f[i][j]=(f[i][j]+f[i-1][k])%p;因此我们要n次计算由v[][]主导的转移,类似与跑n次floyed。因此可以用快速幂优化。
而且我们建立v[][]后,实际就是状态之间的转移有向图,我们用0代表不能转移,用1代表可以转移。也就形成了一个01可达矩阵。
在图论里面,这个矩阵的n次幂的意义就是经过n步后可达的方案数,可以说是状压dp,也可以说是图论的题目。
参考代码:
#include<bits/stdc++.h>
#define Ll long long
using namespace std;
struct jv{
Ll a[64][64];
jv(){memset(a,0,sizeof a);}
}v,a;
bool ok[64];
int bb[10],b[10];
Ll n,m,k,w,ans,mo=1e9+7;
void cunt(int x){//统计是否有小于k个c
int y=x,s=0;
while(y)s+=y%2,y/=2;
if(s<=k)ok[x]=1;
// cout<<x<<" ok"<<ok[x]<<" ; ";
}
void workv(){
int x;
for(int i=0;i<=w;i++)cunt(i);
for(int i=0;i<=w;i++){
if(ok[i]<=k){
int j=i*2%(w+1);
if(ok[j])v.a[i][j]=1;
j++; j%=(w+1);
if(ok[j])v.a[i][j]=1;
}
}
}
jv cheng(jv a,jv b){
jv c;
for(int i=0;i<=w;i++)
for(int j=0;j<=w;j++)
for(int k=0;k<=w;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%mo;
return c;
}
jv ksm(jv x,Ll y){
jv ans=x;
for(y--;y;y>>=1,x=cheng(x,x))
if(y&1)ans=cheng(ans,x);
return ans;
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&k);
w=(1<<m)-1;
// dfs(1);
workv();
a=ksm(v,n);
for(int i=0;i<=w;i++)if(ok[i])ans=(ans+a.a[i][i])%mo;
printf("%lld",ans);
}
不写矩阵幂可以得70分。
#include<bits/stdc++.h>
#define Ll long long
using namespace std;
int v[65][65],ok[65];//ok存储当前状态是否可行,v存储当前状态是否可以转移。
Ll f[100010][165];
Ll n,m,k,w,ans,mo=1e9+7;
void cunt(int x){//统计是否有小于k个c
int y=x;
while(y)ok[x]+=y%2,y/=2;
// cout<<x<<" ok"<<ok[x]<<" ; ";
}
void workv(){
int x;
for(int i=0;i<w;i++)cunt(i);
for(int i=0;i<w;i++){
if(ok[i]<=k){
int j=i*2%w;
if(ok[j]<=k)v[i][j]=1;
j++; j%=w;
if(ok[j]<=k)v[i][j]=1;
}
}
}
void work(int x){
memset(f,0,sizeof f);
f[m][x]=1;
for(int i=m+1;i<=n+m;i++)
for(int j=0;j<w;j++)
for(int k=0;k<w;k++)
if(v[j][k])f[i][j]=(f[i][j]+f[i-1][k])%mo;//进行n次v[j][k]yuan
ans=(ans+f[n+m][x])%mo;
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&k);
w=(1<<m);
workv();
for(int i=0;i<w;i++)if(ok[i]<=k)work(i);
printf("%lld",ans);
}