题目
问题描述
我们把一个数称为有趣的,当且仅当:
1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前。
3. 最高位数字不为0。
因此,符合我们定义的最小的有趣的数是2013。除此以外,4位的有趣的数还有两个:2031和2301。
请计算恰好有n位的有趣的数的个数。由于答案可能非常大,只需要输出答案除以1000000007的余数。
输入格式
输入只有一行,包括恰好一个正整数n (4 ≤ n ≤ 1000)。
输出格式
输出只有一行,包括恰好n 位的整数中有趣的数的个数除以1000000007的余数。
样例输入
4
样例输出
3
我的代码
#include <iostream>
#include <cstring>
using namespace std;
long long dps[4];//因为题目中明确可能性会超过十亿零七,所以用long long数据类型存储,该数据类型取值范围为-2^63~2^63-1,可以承受
long long dpsq[1001][2][2][2][2][2][4];
long long dp(int n,bool flag,int branch) {
int flag0=(dps[0]>0),flag1=(dps[1]>0),flag2=(dps[2]>0),flag3=(dps[3]>0);
long long ans=0;
if(dpsq[n][flag0][flag1][flag2][flag3][flag][branch]!=-1)
return dpsq[n][flag0][flag1][flag2][flag3][flag][branch];
if(n<4&&flag)
return 0;
if(n<=1)
{
if(dps[0]>0&&dps[1]>0&&dps[3]>0&&n==1)
return 1;
else
return 0;
}
else
{
if(flag)
{
ans+=2*dp(n-1,true,0);
dps[1]++;
ans+=dp(n-1,false,1);
dps[1]--;
dps[3]++;
ans+=dp(n-1,false,3);
dps[3]--;
}
else
{
if(branch==1)
{
if(dps[3]>0)
{
dps[2]++;
ans+=dp(n-1,false,branch);
dps[2]--;
}
if(dps[2]==0)
{
dps[3]++;
ans+=dp(n-1,false,branch);
dps[3]--;
}
dps[0]++;
ans+=dp(n-1,false,branch);
dps[0]--;
}
else
{
if(dps[1]>0)
{
dps[0]++;
ans+=dp(n-1,false,branch);
dps[0]--;
}
if(dps[0]==0)
{
dps[1]++;
ans+=dp(n-1,false,branch);
dps[1]--;
}
dps[2]++;
ans+=dp(n-1,false,branch);
dps[2]--;
}
}
}
ans %= 1000000007;
dpsq[n][flag0][flag1][flag2][flag3][flag][branch]=ans;
return ans;
}
int main() {
int n=false;
cin >> n;
dps[0]=dps[1]=dps[2]=dps[3]=0;
memset(dpsq,-1,sizeof(dpsq));
long long ans = dp(n,true,0);
cout << ans << endl;
return 0;
}
我的思路
基本思路是动态规划,因为考虑到最终结果肯定超过10亿,然后如果用现有计数器一个个加的方法,要至少加10亿次,所以如果不用动态规划肯定超时。
然后对于一个n位有趣的数,递归考虑的话
- 可以由一个n-1位有趣的数加3或者1组成
- 可以由一个n-1位缺乏1或者缺乏3但是满足规则顺序的数加上1或者3组成
在这种情况下,结合动态规划的思想,我要考虑把递归函数的结果存储起来,然后再考虑递归过程
这里有点些许复杂的是n-1位非递归的数的递归思路,这里比较容易推出,画递归树可以清晰得到,不赘述
然后结合递归树,得到整个代码的递归过程
递归出口:
- 之前已经计算过的函数值
- 当n比4小同时计算的是有趣的数,4位以下有趣的数不存在,返回0
- 当n递归到1,结束,这时如果监测到除了第一位后面的数位除了2都齐全,就返回1,因为最高位经过分析只能为2,否则返回0,不满足4个数至少出现一次的规则
递归过程
- 第一条路,要计算有趣的数
- 第二条路,计算非有趣的数走不存在1的分支
- 第三条路,计算非有趣的数走不存在3的分支
这道题我的思路解出来AC好像要快一些,显示是0ms,但是有点疑惑到底是的确是快,还是其他原因,这一点有没有知道的人希望评论区告诉我,谢谢
参考答案及注释解析
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <deque>
#include <list>
using namespace std;
long long f[2000][3][2];//因为题目中明确可能性会超过十亿零七,所以用long long数据类型存储,该数据类型取值范围为-2^63~2^63-1,可以承受
int dp(int n, int p1, int p3) {
long long &now = f[n][p1][p3];
if (now != -1) return now;
if (n == 0) {
if (p1 == 2 && p3 == 1) {
now = 1;
} else {
now = 0;
}
return now;
}
now = 0;
if (p1 == 0) {
now += dp(n-1, 1, p3);
} else if (p1 == 1) {
now += dp(n-1, 1, p3);
now += dp(n-1, 2, p3);
} else {
now += dp(n-1, 2, p3);
}
if (p3 == 0) {
now += dp(n-1, p1, p3);
now += dp(n-1, p1, 1);
} else {
now += dp(n-1, p1, 1);
}
now %= 1000000007;
}
int main() {
int n;
cin >> n;
memset(f, -1, sizeof(f));
//初始化三维数组f,每个元素都为-1
int ans = dp(n - 1, 0, 0);
cout << ans << endl;
return 0;
}