dp+计数 poj-1037-A decorative fence

题目链接:

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值