题目
问题描述
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i]<a[2i-1], a[2i+1]>a[2i]。
小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
输入格式
输入一行包含两个整数 m,n。
输出格式
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
3 4
样例输出
14
样例说明
以下是符合要求的摆动序列:
2 1 2
2 1 3
2 1 4
3 1 2
3 1 3
3 1 4
3 2 3
3 2 4
4 1 2
4 1 3
4 1 4
4 2 3
4 2 4
4 3 4
评测用例规模与约定
对于 20% 的评测用例,1 <= n, m <= 5;
对于 50% 的评测用例,1 <= n, m <= 10;
对于 80% 的评测用例,1 <= n, m <= 100;
对于所有评测用例,1 <= n, m <= 1000。
这道题我看网上很多大佬的解法都很巧妙,我这里分享一下我对这道题的解法,这道题暴力求解肯定不能通过所有的结点。所以时要用到动态规划的思想。
首先假设d[i][j]为第i列中以j结尾的方案总数。
此时有两种情况。
1.当i是奇数时,这时如果以j结尾说明它前面一位数的值一定小于j所以方案数就为前面一行所有以小于j结尾的方案数。
即 d[i][j]=sum(d[i-1][1]+…+d[i-1][j-1]);
2.当i是偶数时,这时如果以j结尾说明它前面一位数的值一定大于j所以方案数就为前面一行所有以大于j结尾的方案数。
即 d[i][j]=sum(d[i-1][n]+…+d[i-1][j+1]);
此时我们很容易就写出了动态规划的基础代码
//动态规划解
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <stdlib.h>
#define MAXN 1000005
#define pi 10000
using namespace std;
int m,n;
int dp[1005][1005]; //记录,表示第i列以j数字结尾的总数 时间复杂度O(n^3
int main()
{
cin>>m>>n;
memset(dp,0,sizeof(0));
for(int i=1;i<=n;i++)
{
dp[1][i]=1;
}
for(int i=2;i<=m;i++)
{
if(i%2==0)
{
for(int j=1;j<n;j++)
{
for(int k=j+1;k<=n;k++)
{
dp[i][j]+=dp[i-1][k];
dp[i][j]%=pi;}
}
}
if(i%2==1)
{
for(int j=2;j<=n;j++)
{
for(int k=j-1;k>=1;k--)
{
dp[i][j]+=dp[i-1][k];
dp[i][j]%=pi;}
}
}
}
long sum=0;
for(int i=1;i<=n;i++)
{
sum+=dp[m][i];
sum%=pi;
}
cout<<sum<<endl;
return 0;
}
可是此时的时间复杂都依然达到了O(n^3),显然不能全部通过此题,
于是要进行代码的优化,我们观察奇数列d[i][j]=sum(d[i-1][1]+…+d[i-1][j-1]);
可知,等式右边不就等于前面一列小于j结尾的方案数的和嘛,这就相当于1-(j-1)的前缀和。
于是我们可以重新考虑状态转移方程
用dp1[i][j]表示第奇数列以从1 — j 的方案数的前缀和;
用dp2[i][j]表示第偶数列以从1 — j 的方案数的前缀和;
奇数列有如下的状态转移
dp1[i][j]=dp2[i-1][j-1]+dp1[i][j-1];
解释:
dp1[i][j]分为两个部分,一个就是1—(j-1)的前缀和加上以j结尾的所有方案数,而以j结尾的方案数显然等于前一列所有小于j-1的方案数即前一列1-(j-1)的前缀和即dp2[i-1][j-1]。
偶数列同样分析有,
dp2[i][j]=dp1[i-1][n]-dp1[i-1][j]+dp2[i][j-1];
即可的代码
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <stdlib.h>
#define MAXN 1005
#define pi 10000
using namespace std;
typedef long long ll;
int n,m;
ll dp1[MAXN][MAXN]; //保存的是奇数列前缀和
ll dp2[MAXN][MAXN]; //保存的是偶数列的前缀和;
int main()
{
cin>>m>>n;
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
for(int i=1;i<=n;i++)
dp1[1][i]=i;
for(int i=2;i<=m;i++)
{
if(i%2==0)
{
for(int j=1;j<=n;j++)
{
dp2[i][j]=dp1[i-1][n]-dp1[i-1][j]+dp2[i][j-1];
}
if(dp2[i][1]>pi)
for(int j=1;j<=n;j++)
dp2[i][j]%=pi;
}
else
{
for(int j=1;j<=n;j++)
{
dp1[i][j]=dp2[i-1][j-1]+dp1[i][j-1];
}
if(dp1[i][2]>pi)
for(int j=2;j<=n;j++)
dp1[i][j]%=pi;
}
}
if(m%2)
cout<<dp1[m][n]%pi<<endl;
else
cout<<dp2[m][n]%pi<<endl;
return 0;
}