Pendant
Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 661 Accepted Submission(s): 331
Output the answer taken modulo 1234567891.
Technical Specification
1 ≤ T ≤ 10
1 ≤ N ≤ 1,000,000,000
1 ≤ K ≤ 30
這道題是一道比較經典的矩陣優化DP的題,非常經典,是很值得鑽研的一道題。
這道題的意思是給你k種珍珠,用k種珍珠串成長度爲1,2~n的方法數之和。
我們可以用dp[i][j]表示用j中珍珠構成長度爲i的項鍊的方法總數。則我們考慮長度爲i-1的項鍊的情況,此時我們如果用j種珍珠構成了長度爲i-1的項鍊,那麼用j種珍珠構成長度爲i的項鍊時,需要再對長度爲i-1的項鍊再加上的珍珠是j種珍珠之中一種,所以此時構成長度爲i的項鍊共有dp[i-1][j]*j種,當然還有一種情況是用j-1種珍珠構成長度爲i-1的項鍊,那麼要用j中珍珠構成長度爲i的項鍊,就只需要給長度爲i-1的項鍊再添加上剩下(k-j+1)種中的一種,所以此時構成長度爲i的項鍊有dp[i-1][j-1]*(k-j+1)。
因此可以寫出狀態轉移方程,dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1]*(k-j+1)。
根據這個狀態轉移方程以及n的數量級,我們就要想到用矩陣快速冪來加速dp的過程。但是進行矩陣快速冪時我們需要求的值是dp[k][1]+dp[k][2]+……+dp[k][n]的值。如果在構造矩陣時只是對單個dp的值進行記錄的話,那麼就勢必要對矩陣多項式進行求解。但是如果採用二分矩陣多項式進行遞歸求解容易導致遞歸次數太多,出現棧溢出。因而要在矩陣快速冪時就計算dp之和的值,這樣只用一次矩陣快速冪即可求出所有dp的和。
因此在做和矩陣相關的題的時候就需要格外注意,如果可以一次性通過矩陣快速冪求出某個式子之和,那麼就儘可能不要去使用二分矩陣多項式。因此要構造性能更優的矩陣,來降低時間複雜度。這一點需要好好體會纔行。如何構造矩陣還是很值得思考的知識點。
參考代碼:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<ctime>
#include<cstdlib>
#include<iomanip>
#include<utility>
#define pb push_back
#define mp make_pair
#define CLR(x) memset(x,0,sizeof(x))
#define _CLR(x) memset(x,-1,sizeof(x))
#define REP(i,n) for(int i=0;i<n;i++)
#define Debug(x) cout<<#x<<"="<<x<<" "<<endl
#define REP(i,l,r) for(int i=l;i<=r;i++)
#define rep(i,l,r) for(int i=l;i<r;i++)
#define RREP(i,l,r) for(int i=l;i>=r;i--)
#define rrep(i,l,r) for(int i=1;i>r;i--)
#define read(x) scanf("%d",&x)
#define put(x) printf("%d\n",x)
#define ll long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<11
using namespace std;
const int mod=1234567891;
struct mat
{
ll d[35][35];
}A,B,E;
int t,n,k;
mat multi(const mat &a,const mat &b)
{
mat ans;
REP(i,0,k+1)
{
REP(j,0,k+1)
{
ans.d[i][j]=0;
REP(l,0,k+1)
{
if(a.d[i][l]&&b.d[l][j])
ans.d[i][j]=(ans.d[i][j]+a.d[i][l]*b.d[l][j])%mod;
}
}
}
return ans;
}
mat quickmulti(mat a,int n)
{
if(n==0) return E;
if(n==1) return a;
mat ans=E;
while(n)
{
if(n&1)
{
n--;
ans=multi(ans,a);
}
else
{
n>>=1;
a=multi(a,a);
}
}
return ans;
}
int main()
{
CLR(E.d);
REP(i,0,32)
E.d[i][i]=1;
read(t);
while(t--)
{
read(n);read(k);
CLR(A.d);CLR(B.d);
A.d[k+1][k+1]=1,A.d[k+1][k-1]=1,A.d[k+1][k]=k; //這幾個值是爲了最終就算出所有的dp之和
REP(i,1,k)
A.d[i][i-1]=k-i+1,A.d[i][i]=i;
B.d[1][0]=k;B.d[k+1][0]=0;
if(k==1) B.d[k+1][0]=1; //B.d[k+1][0]這個記錄的是dp[k][1]的值,這也是最初始狀態的值。但是當k==1時,dp[1][0]=1,所以這個情況比較特殊
mat ans=quickmulti(A,n-1);
ans=multi(ans,B);
printf("%I64d\n",ans.d[k+1][0]);
}
}