https://leetcode-cn.com/problems/student-attendance-record-ii/
思路一:动态规划,
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k表示前
i
i
i次出勤缺勤了
j
j
j次且最后连续
k
k
k次迟到情况下能获得出勤奖励的次数。因为当且仅当
j
<
2
&
&
k
<
3
j<2 \&\&k<3
j<2&&k<3时才有意义,所以总体复杂度依然是
O
(
n
)
O(n)
O(n)的。下面是状态转移方程的推导,我们可以分别考虑
j
=
0
j=0
j=0和
j
=
1
j=1
j=1的情况,首先是
j
=
0
&
&
k
=
0
j=0\&\&k=0
j=0&&k=0的情况,即第
n
n
n次到场,有转移方程:
d
p
i
,
0
,
0
=
d
p
i
−
1
,
0
,
0
+
d
p
i
−
1
,
0
,
1
+
d
p
i
−
1
,
0
,
2
dp_{i,0,0}=dp_{i-1,0,0}+dp_{i-1,0,1}+dp_{i-1,0,2}
dpi,0,0=dpi−1,0,0+dpi−1,0,1+dpi−1,0,2
当
k
>
0
k>0
k>0时,显然前
i
−
1
i-1
i−1次出勤最后必须有连续的
k
−
1
k-1
k−1次迟到,有转移方程:
d
p
i
,
0
,
k
=
d
p
i
−
1
,
0
,
k
−
1
dp_{i,0,k}=dp_{i-1,0,k-1}
dpi,0,k=dpi−1,0,k−1
接下来考虑
j
=
1
&
&
k
=
0
j=1\&\&k=0
j=1&&k=0的情况,此时我们第
n
n
n次不仅可以选择到场还可以选择缺勤,有转移方程:
d
p
i
,
1
,
0
=
d
p
i
−
1
,
1
,
0
+
d
p
i
−
1
,
1
,
1
+
d
p
i
−
1
,
1
,
2
+
d
p
i
−
1
,
0
,
0
+
d
p
i
−
1
,
0
,
1
+
d
p
i
−
1
,
0
,
2
dp_{i,1,0}=dp_{i-1,1,0}+dp_{i-1,1,1}+dp_{i-1,1,2}+dp_{i-1,0,0}+dp_{i-1,0,1}+dp_{i-1,0,2}
dpi,1,0=dpi−1,1,0+dpi−1,1,1+dpi−1,1,2+dpi−1,0,0+dpi−1,0,1+dpi−1,0,2
当
k
>
0
k>0
k>0时,和
j
=
0
j=0
j=0的情况是类似的,略过。显然最后的答案就是
∑
j
,
k
d
p
n
,
j
,
k
\sum_{j,k}dp_{n,j,k}
∑j,kdpn,j,k。
class Solution {
public:
int checkRecord(int n) {
// 前i天有j个A且最后有连续k个L 可获得出勤奖励的情况数
const int mod=1e9+7;
using ll=long long;
vector<array<array<ll,3>,2>> dp(n+1);
dp[1][0][0]=dp[1][0][1]=dp[1][1][0]=1;
for(int i=2;i<=n;i++)
{
for(int k=0;k<3;k++)
{
dp[i][0][0]=(dp[i][0][0]+dp[i-1][0][k])%mod;
dp[i][1][0]=(dp[i][1][0]+dp[i-1][1][k]+dp[i-1][0][k])%mod;
}
for(int k=1;k<3;k++)
{
dp[i][0][k]=dp[i-1][0][k-1];
dp[i][1][k]=dp[i-1][1][k-1];
}
}
ll ans=0;
for(int j=0;j<2;j++)
for(int k=0;k<3;k++)
ans+=dp[n][j][k];
return ans%mod;
}
};
思路二:矩阵快速幂。考虑把思路一中后二维合并起来,即
d
p
i
,
j
′
=
d
p
i
,
j
/
3
,
j
%
3
dp_{i,j}'=dp_{i,j/3,j\%3}
dpi,j′=dpi,j/3,j%3。那么可以得到以下的关系:
d
p
n
,
0
=
d
p
n
−
1
,
0
+
d
p
n
−
1
,
1
+
d
p
n
−
1
,
2
d
p
n
,
1
=
d
p
n
−
1
,
0
d
p
n
,
2
=
d
p
n
−
1
,
1
d
p
n
,
3
=
d
p
n
−
1
,
0
+
d
p
n
−
1
,
1
+
d
p
n
−
1
,
2
+
d
p
n
−
1
,
3
+
d
p
n
−
1
,
4
+
d
p
n
−
1
,
5
d
p
n
,
4
=
d
p
n
−
1
,
3
d
p
n
,
5
=
d
p
n
−
1
,
4
\begin{array}{l} dp_{n,0}=dp_{n-1,0}+dp_{n-1,1}+dp_{n-1,2} \\ dp_{n,1}=dp_{n-1,0} \\ dp_{n,2}=dp_{n-1,1} \\ dp_{n,3}=dp_{n-1,0}+dp_{n-1,1}+dp_{n-1,2} + dp_{n-1,3}+dp_{n-1,4}+dp_{n-1,5} \\ dp_{n,4}=dp_{n-1,3} \\ dp_{n,5}=dp_{n-1,4} \\ \end{array}
dpn,0=dpn−1,0+dpn−1,1+dpn−1,2dpn,1=dpn−1,0dpn,2=dpn−1,1dpn,3=dpn−1,0+dpn−1,1+dpn−1,2+dpn−1,3+dpn−1,4+dpn−1,5dpn,4=dpn−1,3dpn,5=dpn−1,4
用矩阵乘法表达就是:
d
p
n
=
d
p
n
−
1
,
0
d
p
n
−
1
,
1
d
p
n
−
1
,
2
d
p
n
−
1
,
3
d
p
n
−
1
,
4
d
p
n
−
1
,
5
∗
1
1
0
1
0
0
1
0
1
1
0
0
1
0
0
1
0
0
0
0
0
1
1
0
0
0
0
1
0
1
0
0
0
1
0
0
dp_n= \begin{array}{l} dp_{n-1,0} & dp_{n-1,1} & dp_{n-1,2}& dp_{n-1,3}& dp_{n-1,4} & dp_{n-1,5} & \end{array} * \begin{array}{l} 1 & 1 & 0 & 1 & 0 & 0\\ 1 & 0 & 1 & 1 & 0 & 0\\ 1 & 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1 & 1 & 0\\ 0 & 0 & 0 & 1 & 0 & 1\\ 0 & 0 & 0 & 1 & 0 & 0\\ \end{array}
dpn=dpn−1,0dpn−1,1dpn−1,2dpn−1,3dpn−1,4dpn−1,5∗111000100000010000111111000100000010
设该矩阵为
M
M
M,可得:
d
p
n
=
d
p
1
∗
M
n
−
1
dp_n=dp_1*M^{n-1}
dpn=dp1∗Mn−1。因此利用矩阵快速幂可以把时间复杂度降低到
O
(
l
g
n
)
O(lgn)
O(lgn)。
class Matrix{
public:
using ll=long long;
Matrix(){}
Matrix(const array<array<ll,6>,6> &mx):m(mx){}
array<ll,6>& operator[](int i)
{
return m[i];
}
void init()
{
for(int i=0;i<6;i++)
m[i][i]=1;
}
void operator *=(Matrix mb)
{
int mod=1e9+7;
Matrix ma;
for(int i=0;i<6;i++)
{
for(int j=0;j<6;j++)
{
for(int k=0;k<6;k++)
{
ma[i][j]+=(m[i][k]*mb[k][j]);
ma[i][j]%=mod;
}
}
}
*this=ma;
}
private:
array<array<ll,6>,6> m{};
};
Matrix qpow(int n, Matrix m)
{
Matrix ans;
ans.init();
while(n)
{
if(n&1)
ans*=m;
m*=m;
n>>=1;
}
return ans;
}
class Solution {
public:
int checkRecord(int n) {
const int mod=1e9+7;
Matrix dp;
dp[0][0]=dp[0][1]=dp[0][3]=1;
Matrix m({1,1,0,1,0,0,1,0,1,1,0,0,1,0,0,1,0,0,0,0,0,1,1,0,0,0,0,1,0,1,0,0,0,1,0,0});
dp*=qpow(n-1,m);
long long ans=0;
for(int i=0;i<6;i++)
ans+=dp[0][i];
return ans%mod;
}
};