题目链接:http://poj.org/problem?id=1037
理解了牛人的代码才明白的,花了一晚上的功夫,orz....跪dp.
思路:dp[len[i][0]表示长度为len的以i开始的前两个下降的序列的个数,dp[len][i][1]表示长度为len的以i开始的前两个上升的序列的个数;
则有
dp[len][i][0]+=dp[len-1][j][1](j<i);
dp[len][i][1]+=dp[len-1][j][0](j>i);
预处理之后,把序列求出来就可以了(这是难点);
做法:由于不知道开始是上升还是下降,这一开始枚举,确定第1位的数,以及升降情况,然后我们就可以根据开始求出的数,来枚举下一位(这里还用到一个技巧,就是如果一个映射关系,代码以注释)。
View Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 typedef long long ll; 6 ll dp[21][21][2]; 7 bool mark[21]; 8 /* 9 dp[len][i][0]表示长度为len的序列,第i长的棒起头,前两根下降的方案数; 10 dp[len][i][1]表示相应的前两根上升的方案数 11 */ 12 13 14 int main(){ 15 dp[1][1][0]=dp[1][1][1]=1; 16 for(int len=2;len<=20;len++){ 17 for(int i=1;i<=len;i++){ 18 for(int j=1;j<i;j++)dp[len][i][0]+=dp[len-1][j][1]; 19 for(int j=i;j<=len;j++)dp[len][i][1]+=dp[len-1][j][0]; 20 } 21 } 22 int _case,n,left,right,j; 23 scanf("%d",&_case); 24 while(_case--){ 25 ll m; 26 bool flag=false; 27 scanf("%d%lld",&n,&m); 28 memset(mark,false,sizeof(mark)); 29 for(int i=1;i<=n&&!flag;i++){ 30 //先处理降的 31 for(j=0;j<=1;j++){ 32 m-=dp[n][i][j]; 33 if(m<=0){ 34 m+=dp[n][i][j]; 35 mark[i]=true; 36 printf("%d",i); 37 //如果是降的,那么紧接后面的数范围为1~i-1; 38 //反之,则为i+1~n; 39 if(j==0){left=1;right=i-1;} 40 else {left=i+1;right=n;} 41 flag=true; 42 j^=1; 43 break; 44 } 45 } 46 } 47 for(int len=n-1;len>=1;len--){ 48 int count=0; 49 //相当于是一个映射,把没标记过的数一一映射到1~len 50 for(int i=1;i<left;i++)if(!mark[i])count++;//统计第num位开始可以开始的数 51 for(int i=left;i<=right;i++)if(!mark[i]){ 52 count++;//i对应的1~len中的映射,纸上YY就明白了。。。 53 m-=dp[len][count][j]; 54 if(m<=0){ 55 m+=dp[len][count][j]; 56 mark[i]=true; 57 printf(" %d",i); 58 if(j==0){left=1;right=i-1;} 59 else {left=i+1;right=n;} 60 j^=1; 61 break; 62 } 63 } 64 65 } 66 puts(""); 67 } 68 return 0; 69 }