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]);
}
}