题目链接:传送门
可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:
'A':Absent,缺勤
'L':Late,迟到
'P':Present,到场
如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:按总出勤计,学生缺勤('A')严格 少于两天。
学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。
给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 1e9 + 7 取余的结果。
自己琢磨了半天写了一个DP+容斥,回头一看题解,好家伙dp方程完全不一样,还以不用容斥,还可以用矩阵快速幂搞,直接把复杂度优化到LogN,点赞。
const long long mod=1e9+7;
class Solution {
public:
int checkRecord(int n) {
if(n==1) return 3;
if(n==2) return 8;
long long dp[100005][3][2];
/*
第二维选择的状态,0=P 1=L 2=A,第三维表示是否选了1
dp[i][0][0] = 当前位置i选择了P,并且0~i之前没有一次A缺勤的所有可能数
dp[i][0][1] = 当前位置i选择了P,并且0~i刚好有一次A缺勤的所有可能数
dp[i][1][0] = 当前位置i选择了L,并且0~i之前没有一次A缺勤的所有可能数
dp[i][1][1] = 当前位置i选择了L,并且0~i刚好有一次A缺勤的所有可能数
dp[i][2][1] = 当前位置i选择了A,并且0~i刚好有一次A缺勤的所有可能数,选择A就肯定有A,所有不考虑dp[i][2][0]
*/
//初始状态
dp[0][0][0]=1;//P
dp[0][0][1]=0;//
dp[0][1][0]=1;//L
dp[0][1][1]=0;//
dp[0][2][1]=1;//A
dp[1][0][0]=2;//PP,LP
dp[1][0][1]=1;//AP
dp[1][1][0]=2;//PL,LL
dp[1][1][1]=1;//AL
dp[1][2][1]=2;//PA,LA
long long sum_L0=0,sum_L1=0;
for(int i=2;i<n;i++){
//当前位置选P,可以由上一个推导过来
dp[i][0][0]=(dp[i-1][0][0]+dp[i-1][1][0])%mod;
dp[i][0][1]=(dp[i-1][0][1]+dp[i-1][1][1]+dp[i-1][2][1])%mod;
//当前位置选L,同样,但是注意连续三个L,要减去上上层选择L的
dp[i][1][0]=(dp[i-1][0][0]+dp[i-1][1][0]-dp[i-2][1][0]+sum_L0)%mod;
//容斥了一下
sum_L0=(dp[i-2][1][0]-sum_L0+mod)%mod;
dp[i][1][1]=(dp[i-1][0][1]+dp[i-1][1][1]+dp[i-1][2][1]-dp[i-2][1][1]+sum_L1)%mod;
//再容斥了一下
sum_L1=(dp[i-2][1][1]-sum_L1+mod)%mod;
//当前位置选A,只能从上一层没有A的地方来
dp[i][2][1]=(dp[i-1][0][0]+dp[i-1][1][0])%mod;
}
return (dp[n-1][0][0]+dp[n-1][0][1]+dp[n-1][1][0]+dp[n-1][1][1]+dp[n-1][2][1])%mod;
}
};