题目链接:
http://poj.org/problem?id=1037
题目大意:
给一个n,求出1~n组成波浪形按字典序的第m种序列。
解题思路:
经典dp+计数。
之前做个一道,只需求所有的种数,但现在要求第m种,所以要考虑计数,所以不能仅以长度和上升下降两维来处理,还需增加一维表示以某个开始。
dp[i][j][0]表示长度为i,以第j长的开始,且前两个为下降的种数。只和相对长度有关,故此处j指第j长的。1表示上升。
重点是计数,先找从小到大找第1个,如果m<=该数开始的种数时,说明第一位一定是该数。
找到第一位后,记录后面一位的取值范围,在这个范围内依次找第二位,找的时候如果m>=该数开始的种数,则去掉该种数,继续往后找。
代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/
ll dp[22][22][2]; //dp[i][j][0]表示长度为i时,第j长的作为第一位且前两位下降的种数
//dp[i][j][1]表示长度为i时,第j长的作为第一位且前两位上升的种数
bool vis[22];
int main()
{
dp[1][1][0]=dp[1][1][1]=1;
for(int i=2;i<=20;i++)
for(int j=1;j<=i;j++) //放到第一位
{
for(int p=1;p<j;p++) //小于j的,不变
dp[i][j][0]+=dp[i-1][p][1];
for(int p=j;p<i;p++) //大于等于j的,依次+1。只和相对高度有关
dp[i][j][1]+=dp[i-1][p][0];
}
ll n,m;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%I64d%I64d",&n,&m);
int l=1,r=n,j;
bool find=false;
memset(vis,false,sizeof(vis));
for(int i=1;i<=n&&!find;i++) //计数
{
for(j=0;j<2;j++)
{
m-=dp[n][i][j];
if(m<=0) //说明找到了首位
{
m+=dp[n][i][j];
vis[i]=true;
printf("%d",i);
if(!j) //下降的形式
l=1,r=i-1;
else
l=i+1,r=n;
find=true;
break;
}
}
}
j^=1; //后面一位肯定是相反形式的
for(int i=n-1;i>=1;i--)//还需要找n-1位
{
int cnt=0;
for(int p=1;p<l;p++)
if(!vis[p])
cnt++; //统计小的,统计l~r之间符合要求数的相对位置
for(int p=l;p<=r;p++)
{
if(!vis[p])
{
cnt++;//相对位置
m-=dp[i][cnt][j];
if(m<=0)//一定是该位
{
m+=dp[i][cnt][j];
vis[p]=true;
printf(" %d",p);
if(!j)
l=1,r=p-1;
else
l=p+1,r=n;
j=j^1;
break;
}
}
}
}
putchar('\n');
}
return 0;
}