P1357 花园
状压dp+矩乘
首先看到题目说M<=5,这么小的数据明显可以用状压保存相邻状态,于是可以得到一个80分的dp:
先筛出所有可用的状态,然后建立一个矩阵保存可转移的状态,再然后把每个状态都当成最初状态各跑一次dp,累计答案
然而我们发现,n太大了。又发现,其实每次转移可以直接用矩乘来搞(用到了状态矩阵)
于是就用矩乘了。嗯,就这样,具体看题解吧。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int mod=1000000007; ll n,ans; int m,k,cnt; int state[65]; struct matrix{ int a[65][65]; matrix(){memset(a,0,sizeof(a));} matrix operator * (matrix &tmp){ matrix c; for(int i=1;i<=cnt;++i) for(int j=1;j<=cnt;++j) for(int k=1;k<=cnt;++k) c.a[i][j]=(ll)(c.a[i][j]+(ll)a[i][k]*tmp.a[k][j]%mod)%mod; return c; } matrix ksm(matrix x,ll y){ matrix ans; for(int i=1;i<=cnt;++i) ans.a[i][i]=1; for(;y;y>>=1){ if(y&1) ans=ans*x; x=x*x; } return ans; } }st,mul; int main(){ scanf("%lld%d%d",&n,&m,&k); for(int i=(1<<m)-1;i>=0;--i){ int t=0; for(int j=i;j;j>>=1) t+=(j&1); if(t<=k) state[++cnt]=i; } //筛出可用状态 for(int i=1;i<=cnt;++i) for(int j=1;j<=cnt;++j) { int x=state[i],y=state[j]>>1,ok=1; for(int k=m-1;k;--k){ if((x&1)!=(y&1)) {ok=0; break;} x>>=1; y>>=1; } mul.a[i][j]=ok; } //可转移状态矩阵 for(int i=1;i<=cnt;++i) st.a[i][i]=1; mul=mul.ksm(mul,n); st=st*mul; for(int i=1;i<=cnt;++i) ans=(ans+st.a[i][i])%mod; //累计矩阵对角线上的答案 printf("%lld",ans); return 0; }