洛谷P3990 [SHOI2013]超級跳馬
阿新 • • 發佈:2020-12-06
題意
分析
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)\)演算法就這樣做出來了
矩陣快速冪
但是我們看到資料範圍時才知這題並不簡單...暗藏殺機...
\(m\leq 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; }