题意
分析
DP部分
这题乍一看是个水题...
可以很快看出(DP)的做法
设(dp[i][j])表示到了第(i)行第(j)列的方案数
那么(dp)转移方程很好写了:
[dp[i][j]=dp[i-1][j]+dp[i-1][j]+dp[i-1][j+1]+dp[i-2][j]
]
可能唯一的难点在于这个第四项吧...我这里解释一下这个方程的含义
我们当这个位置可能由前一列的上一个,前一列的这个,前一列的下一个一步到达(因为限制就是每次只能移动一行或者不动)
同时我们考虑一步到达这个位置的其它点,容易发现这些点都是可以一步到达((i-2,j))的,所以我们把剩下的点的贡献相当于全部都算在了((i-2,j))上面,而没有剩下的点一定存在于前面三项加上的贡献当中
然后这个题的(O(nm))算法就这样做出来了
矩阵快速幂
但是我们看到数据范围时才知这题并不简单...暗藏杀机...
(mleq 10^9) !!!
但是我们观察一下上面的(DP)转移方程,我们可以发现:对于每一列来说,当前这一项只会被前一项和前两项统计到
所以我们可以考虑 状压 矩阵快速幂优化(DP)
我们矩阵要记录的就是上一行和上两行的状态,共(100*100)
然后构造的状态转移矩阵就按照(DP)方程那样把初始矩阵赋一下值就可以了,最后答案也不难得出,见代码即可
时间复杂度(O(nlogm*(2n)^3)),足以通过此题
代码
窝是看了这位巨佬的博客才学会的,如有雷同只是看了就感觉写的都一样了QWQ
#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
const int N=105,MOD=30011;
int n,m;
#define inc(a,b) (a+b>=MOD?a+b-MOD:a+b)
struct Matrix{
int a[N][N];
Matrix(){memset(a,0,sizeof(a));}
Matrix operator *(Matrix B){
Matrix C;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
C.a[i][j]=inc(C.a[i][j],a[i][k]*B.a[k][j]%MOD);
}
}
}
return C;
}
};
Matrix QuickPow(Matrix A,int y){
Matrix res;
for(int i=1;i<=n;i++) res.a[i][i]=1;
while(y){
if(y&1) res=res*A;
A=A*A;
y>>=1;
}
return res;
}
Matrix base;
int ans1,ans2;
int main(){
read(n),read(m);
if(m<=2){
if(n<=2&&m<=n) puts("1");
else puts("0");
return 0;
}
for(int i=1;i<=n;i++){
base.a[i][i-1]=base.a[i][i]=base.a[i][i+n]=base.a[i+n][i]=1;
if(i!=n)base.a[i][i+1]=1;
}
n<<=1;
base=QuickPow(base,m-2);
n>>=1;
if(n==1){
write(base.a[1][1]);
return 0;
}
int n2=n<<1;
ans1=(base.a[1][n2-1]+base.a[2][n2-1]+base.a[n+1][n2-1])%MOD,ans2=(base.a[1][n2]+base.a[2][n2]+base.a[n+1][n2])%MOD;
write(inc(ans1,ans2)%MOD);
return 0;
}