分析:
最关键的是找到状态转移方程,题目中第
i
i
i 天的方案数与第
i
−
1
i-1
i−1 和
i
−
2
i-2
i−2 天的选择有关,不妨假设三家店为:0号,1号与2号。如果前面两天吃的是 0 号店,那么第
i
i
i 天势必不能吃这0号店,方案数就是第
i
−
1
i-1
i−1天吃1号店与2号店的方案数之和与第
i
−
2
i-2
i−2 天吃1号店与2号店的方案数之和。列表说明比较直观:
表中的第一行是天数,第一列是当天选择的店,高亮的8就表示第3天选择0号店的总方案数是8种。
那么8是如何计算得到的呢?框出来的部分就是计算8所需要的数据,被框起来的是此前两天选择1号店和2号店的总方案数,题目规定不能三天连吃同一家店,因此,从第三天起,就要考虑前面的选择对后续选择的影响。
第一天时无论选择哪种方案,总方案数都是1(这里说的总方案数是某天选择某一家店的总方案数,题目要求的答案是所有方案,选择三家店的总方案数要加起来);
第二天是可以和第一天吃同一家店的,因此第二天的那一列每一行的计算都是前一列三家店总方案数的和,或者说第二天的选择是不受限的,因此第一天吃哪家第二天都可以吃0号、1号、2号。
但第三天就不一样了,以第三天选择0号为例,同一行代表的是第一天吃0号店、第二天吃0号店,那么第三天显然不能继续吃0号店,因此第一行的前两格对于第三格的计算是没有贡献的,而第一天吃1号或者2号店(第一列的2、3格)就保证了无论第二天吃哪家,第三天都可以吃0号,因此方案数都对第三天吃0号这一格有贡献,具体来说就是需要加和,第二天同理,第二天吃1号或2号就可以保证无论第一天吃哪家,第三天都可以吃0号,也有贡献,也要加进结果,因此最终的8是由这四个部分加和得到的。
因为动态规划的计算过程是可列表的,因此可以现将题目范围天数计算出来,再根据读入的天数查表输出。
单写一下递推公式:dp[day][j]表示在day这天吃j号的可能方案总数。
dp[day][j] = dp[day - 1][(j + 1) % 3] + dp[day - 1][( j + 2 ) % 3] + dp[day - 2][(j + 1) % 3] + dp[day - 2][(j + 1) % 3]
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;//查错半天结果是模数打错了。。写成1e9 + 10了,结果差了超级多。。
typedef long long ll;//8 bytes 2400000B = 24000K
ll f[100010][3];
void dp()
{
memset(f, 0, sizeof(f));
f[1][0] = 1; f[1][1] = 1; f[1][2] = 1;
f[2][0] = 3; f[2][1] = 3; f[2][2] = 3;
int day = 3;
for( ; day <= 100000; day++)
{
for(int j = 0; j < 3; j++)
{
f[day][j] = (f[day - 1][(j + 1) % 3] + f[day - 1][(j + 2) % 3] + f[day - 2][(j + 1) %3] +f[day - 2][(j + 2) % 3]) % mod;
}
}
}
int main()
{
int T;
cin>>T;
dp();
while(T--)
{
int n;
cin>>n;
cout<<(f[n][0] % mod + f[n][1] % mod + f[n][2] % mod) % mod<<endl;
}
return 0;
}
提交记录:
WA了两次,第一次纯属思路错误,因为写成了三列的形式,所以不需要在计算过程中乘以3,只需要在最后输出时对天数所在行进行求和取模;第二次才发现模数打错了,习惯是开数组的时候直接加10余量,打模数那行的时候没过脑子,模数虽然只差了3,但是累积下来最后误差差了两倍多,百思不得其解,后来从头读题才突然发现,这种东西还是复制为妙。