题目描述
如果一个序列满足下面的性质,我们就将它称为摆动序列:
- 序列中的所有数都是不大于k的正整数;
- 序列中至少有两个数。
- 序列中的数两两不相等;
- 如果第i – 1个数比第i – 2个数大,则第i个数比第i – 2个数小;如果第i – 1个数比第i – 2个数小,则第i个数比第i – 2个数大。
比如,当k = 3时,有下面几个这样的序列:
1 2
1 3
2 1
2 1 3
2 3
2 3 1
3 1
3 2
一共有8种,给定k,请求出满足上面要求的序列的个数。
输入
输入包含了一个整数k。(k< =20)
输出
输出一个整数,表示满足要求的序列个数。
样例输入
3
样例输出
8
思路:一开始以为是记忆化搜索,但是没想出怎么记忆化,就暴力dfs+回溯试了一下,发现数列收敛的很快,没有特别长的那种,所以时间复杂度是允许的(k<=20)。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxx=2e1+1;
int a[maxx],vis[maxx];
int n;
inline void dfs(int num,int &ans)
{
if(num>n) return ;
if(num>=2)
{
ans++;
if(a[num]>a[num-1])
{
for(int i=a[num-1]-1;i>=1;i--)
{
if(vis[i]==0)
{
a[num+1]=i;
vis[i]=1;
dfs(num+1,ans);
vis[i]=0;
a[num+1]=i;
}
}
}
else
{
for(int i=a[num-1]+1;i<=n;i++)
{
if(vis[i]==0)
{
a[num+1]=i;
vis[i]=1;
dfs(num+1,ans);
vis[i]=0;
a[num+1]=0;
}
}
}
}
else
{
for(int i=a[num]-1;i>=1;i--)
{
if(vis[i]==0)
{
a[num+1]=i;
vis[i]=1;
dfs(num+1,ans);
vis[i]=0;
a[num+1]=0;
}
}
for(int i=a[num]+1;i<=n;i++)
{
if(vis[i]==0)
{
a[num+1]=i;
vis[i]=1;
dfs(num+1,ans);
vis[i]=0;
a[num+1]=0;
}
}
}
}
int main()
{
scanf("%d",&n);
int ans=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
a[1]=i;
vis[i]=1;
dfs(1,ans);
vis[i]=0;
}
cout<<ans<<endl;
return 0;
}
如果k再大一点,dfs就不是那么好了,因为回溯耗时也很大。那么我们考虑dp的方式。我们可以根据dfs的结果,建立一个表,在里面寻找规律。如下图所示(图片来源):
纵坐标代表的是k的取值,横坐标代表的是在这k个数中选取的数的个数。
我们可以发现,横坐标为2的时候,答案总是(k-1)*k。横坐标再大的时候,我们就可以发现规律了。
状态转移方程:dp[i][j]=dp[i-1][j]+dp[i-1][j-1].
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxx=2e2+100;
int dp[maxx][maxx];
int n;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++) dp[i][2]=(i-1)*i;
for(int j=3;j<=n;j++)
{
for(int i=3;i<=n;i++) dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
}
int ans=0;
for(int i=2;i<=n;i++) ans+=dp[n][i];
cout<<ans<<endl;
return 0;
}
时间复杂度仅仅为O(n^2)。
努力加油a啊,(o)/~