问题描述
小明想知道,满足以下条件的正整数序列的数量:
1. 第一项为 n;
2. 第二项不超过 n;
3. 从第三项开始,每一项小于前两项的差的绝对值。
请计算,对于给定的 n,有多少种满足条件的序列。
输入格式
输入一行包含一个整数 n。
输出格式
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
4
样例输出
7
样例说明
以下是满足条件的序列:
4 1
4 1 1
4 1 2
4 2
4 2 1
4 3
4 4
评测用例规模与约定
对于 20% 的评测用例,1 <= n <= 5;
对于 50% 的评测用例,1 <= n <= 10;
对于 80% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 1000。
题目分析:模拟赛的时候看到n很小,并且自己也只会n^3的记忆化搜索,就直接暴力打了个表交上去了,因为题目强调了当前项和上一项之间的关系,所以 dp[ i ][ j ] 代表的就是前一项为 i ,当前项为 j 时的方案数,这样在dfs里再套一层for就很简单的写出来了,n^3的方法就不多说了
重点是如何优化为 n^2 的算法,因为 dp[ i ][ j ] 的两维是无法优化的了,可以着手考虑的是每次dfs里的那一层for循环能否优化掉,这里就可以借助前缀和的思想了,将 dp[ i ][ j ] 所表示的意义转换为:前一项为 i ,当前项为 [ 1 , j ] 时的方案数,这样最后的答案从先前的变为了 dp[ n ][ n ] ,转移方程也比较巧妙(我自己反正推不出来):
dp[ i ][ j ] = dp[ i ][ j - 1 ] + dp[ j ][ abs( i - j ) - 1 ] + 1
如何理解呢,因为 dp[ i ][ j ] 的意义转换为了前缀和的思想,所以 dp[ i ][ j ] 在前一项固定的基础上,应该是从当前项为 j - 1 时转移而来,加上 前一项为 i ,当前项为 j 时的方案数就是 dp[ i ][ j ] 了,最后加上一是因为本身对答案也有贡献,而前一项为 i ,当前项为 j 的答案,就可以利用题目给出的绝对值之差这个条件约束了,妙啊
动态规划的题目一般都是只可意会不可言谈。。
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
//#include<set>
#include<sstream>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e3+100;
const int mod=10000;
int dp[N][N];
int dfs(int pre,int cur)
{
if(cur<=0)
return 0;
if(dp[pre][cur]!=-1)
return dp[pre][cur];
return dp[pre][cur]=(1+dfs(pre,cur-1)+dfs(cur,abs(pre-cur)-1))%mod;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
memset(dp,-1,sizeof(dp));
int n;
scanf("%d",&n);
printf("%d\n",dfs(n,n));
return 0;
}