Solution:
如果您敏锐的注意到了M的范围,那么不难想到状压。
状态压缩一下最后M位的放的花盆的情况。
同时还可以提前与处理一下V[i,j]表示状态i能否转移到状态j。
但是,鉴于它是一个环形的,所以似乎并不好怎么去DP(断环为链似乎也不好弄)。
所以此处用的转移还是特别巧妙的。
不妨这样来看 环 :
如果从一个状态开始,顺次经过n次DP后,又回到了当前状态,就代表这形成了一个环!
那么,可以直接枚举前M个每一种可行的状态,DP到n+M,把和最初状态相同的那个状态的DP值加入ans即可
BF Code:
IL void DP(int state) {
RG int i,j,k;
memset(f,0,sizeof(f));
f[m][state]=1;
for(i=m+1;i<=n+m;++i)
for(j=0;j<=(1<<m)-1;++j) {
if(!ok[j]) continue;
for(k=0;k<=(1<<m)-1;++k)
if(ok[k]&&v[k][j]) (f[i][j]+=f[i-1][k])%=mod;
}
(ans+=f[n+m][state])%=mod;
}
主函数中:
for(i=0;i<=(1<<m)-1;++i)
if(ok[i]) DP(i);
这样可以做到80分。
实际上这一DP转移的过程同样可以看做是矩阵乘法。
怎么说?
不妨把f看做状态矩阵,v看做转移矩阵。
可以发现,当v[x,y]为1时,就对应 从f[i-1,x] 给 f[i,y] 累计贡献,
那么在矩阵乘法中,就相当于 f[1,y] 的更新,会来自于所有的 v[x,y]*f[1,x]。
所以考虑矩阵快速幂加速。
因为暴力DP中,也就可行的初始态的f值为1。
所以直接将转移矩阵v自乘n次,对于每一种可行的初始态i,把矩阵中的MT[i,i]加入答案即可。
Code :
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define int long long
#define DB double
using namespace std;
IL int gi() {
char ch=getchar(); RG int x=0,w=0;
while(ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=33;
const int mod=1e9+7;
int n,m,K,ans,ok[N],v[N][N];
struct Matrix {
int MT[N][N];
Matrix(){memset(MT,0,sizeof(MT));}
IL void NewMT() {
RG int i;
for(i=0;i<N;++i) MT[i][i]=1;
}
Matrix operator *(const Matrix &s) {
RG int i,j,k,w=(1<<m)-1;
RG Matrix ans;
for(i=0;i<=w;++i)
for(j=0;j<=w;++j)
for(k=0;k<=w;++k)
(ans.MT[i][j]+=MT[i][k]*s.MT[k][j]%mod)%=mod;
return ans;
}
}f;
IL Matrix qpow(Matrix x,int p) {
RG Matrix ans;
for(ans.NewMT();p;p>>=1,x=x*x)
if(p&1) ans=ans*x;
return ans;
}
IL bool check(int x) {
RG int cnt=0;
while(x) cnt+=x&1,x>>=1;
return cnt>K;
}
IL void getv(int x) {
RG int y=x>>1;
if((x>>m)&1) x^=1<<m;
if(check(y)||check(x)) return;
ok[x]=ok[y]=1,v[x][y]=1;
}
void dfs(int x,int now) {
if(x==m+2) {getv(now);return;}
dfs(x+1,now),dfs(x+1,now|(1<<x-1));
}
signed main()
{
RG int i,j,w;
n=gi(),m=gi(),K=gi();
w=(1<<m)-1,dfs(1,0);
for(i=0;i<=w;++i)
for(j=0;j<=w;++j) f.MT[j][i]=v[i][j];
// 注意!!! 由于v记录的是i可以转移到j.所以要反过来
// 实际上,这是矩阵乘法的特点所造成的.
for(i=0,f=qpow(f,n);i<=w;++i)
if(ok[i]) (ans+=f.MT[i][i])%=mod;
printf("%lld
",ans);
return 0;
}