poj 1037 前K项固定求方案数, 递推动态规划

  解决此类问题常见思路:

    为求以 A1, A2, ... , AK 为前K项的所有栏杆字典排序第K小的序列.

  这里给出其他牛人的介绍

先抛开此题,用最简单的全排列问题来说明这种方法。如1,23,4的全排列,共有4!种,求第10个的排列是?

先试首位是1,后234有3!=6种<10,说明1偏小,转化成以2开头的第(10-6=4)个排列,而3!=6 >= 4,说明首位恰是2。

第二位先试1(1没用过),后面2!=2个<4,1偏小,换成3(2用过了)为第二位,总编号也再减去2!,剩下2了。而此时2!>=2,说明第二位恰好是3。

第三位先试1,但后面1!<2,因此改用4。末位则是1了。

这样得出,第10个排列是2-3-4-1。(从1计起)

这种方法的核心在于求,给定前缀下后面总的排列数。全排列问题较易求。同时还需记录前面用过的数字。

  

  这里注意到, '高低起伏', 是相邻两个来决定的.

  所以我们状态开设为 G( n, h ) , 且有两种不同状态 up, down

  所以状态为

    G( n, h ).up  意义为 n个数,第一个以高度h开始,第一第二个呈上升的排列方案数

    G( n, h ).down  意义为 n个数,第一个以高度h开始,第一第二个呈下降的排列方案数 

  注意到题目要求 '高低起伏' 

  所以 ( i, i-1 ) 为上升(或下降) 则 ( i-1, i-2 ) 必定为 下降(或上升)

  所以 我们可以得出递推关系式:

    

   

   注意, 

      假设从 1...N中除去 A1,A2,...,Ak 项后剩余 B1,B2,..,B(n-k) ,

      那么方案数目则转换成了求, B1,B2,..,B(n-k)项 头两块木头 上升(或下降)的 栅栏数目.

      又因为所谓的'高低起伏'只是木头与木头的相对高度,而非绝对高度.

      所以对于 N个高度,我们去掉了其中的T后, 剩下N-1不同数字可以映射到 1到n-1.相对为1,2,..,n-1的全排列

      所以方案中的T,为1,2,...,n中的相对第T小. 

      这样做,我们在N变换的过程中可以重复利用前面计算好的值,而节省大量时间.

      还要注意.求A1时,因为字典序是down小,up大, 当down不满足时,也要减去其方案数.

解题代码:

View Code
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;


typedef long long LL;
struct node{
    LL up, down;
}G[25][25];

int p[25], n;
LL C, N;
bool vis[25];

map< int,int > Mp;

// 递归函数获取 后面K个,以高度H为开始的序列方案数

void Getg()
{
    memset(G,0,sizeof(G));    
    for(int i = 1; i <= 1; i++)
        G[1][i].down = G[1][i].up = 1;
    for(int i = 2; i <= 20; i++)
    {
        for(int T = 1; T <= i; T++)
        {
            for(int j = 1; j <= T-1; j++) // 长度I位置的T已经被去掉了
                    G[i][T].down += G[i-1][j].up;    
            for(int j = T; j <= i-1; j++) // 原本的T已经去掉,T+1映射成相对位置T,后面只有i-1个数
                    G[i][T].up += G[i-1][j].down;
        }    
    }
}
int main()
{
    Getg();

    int T;
    scanf("%d", &T);
    while( T-- )
    {
        scanf("%d%lld", &n, &C);
        N = C;
        
        memset( vis, 0, sizeof(vis) );
        memset( p, 0, sizeof(p) );
        int clas;    
        // 单独处理 A1 位置 
        for(int h = 1; h <= n; h++)
        {
            vis[h] = true;
            p[1] = h;    
        
            LL tmp = G[n][h].down+G[n][h].up;
        //    printf("down=%lld,up=%lld\n",G[n][h].down, G[n][h].up);    
            if( tmp >= N ){
                if( G[n][h].down >= N ) clas = 0;
                else{
                    N -= G[n][h].down;    
                    clas = 1;
                }    
                break;
            }
            else    N -= tmp;    
            vis[h] = false;
        
        }
//        printf("p[1] = %d, class = %d, N = %lld\n", p[1], clas, N );    
        // 处理 Ai 位置    
        for(int i = 2; i <= n; i++)
        {
            int cnt = 1; Mp.clear();
            for(int h = 1; h <= n; h++)
                if( !vis[h] ) Mp[h] = cnt++;

            if( clas == 0 ) // (i-1,i) 为down, 则 (i,i+1)必须为 up
            {// G[i-1]位置为 down
                //枚举 Ai
                for(int h = 1; h < p[i-1]; h++)
                    if( !vis[h] ) //未被使用    
                    {
                        int T = Mp[h]; //相对高度第T小    
                        vis[h] = true;
                        p[i] = h;    
                            
                        LL tmp = G[n+1-i][T].up;
                        if( tmp >= N )
                        {
                            clas = 1;    
                            break;    
                        }
                        else N -= tmp;    
                        vis[h] = false;
                    }
            }        
            else // (i-1,i) 为up, 则 (i,i+1) 必须为 down
            {// G[i-1]位置为 up
                for(int h = p[i-1]+1; h <= n; h++)
                    if( !vis[h] )    
                    {
                        int T = Mp[h];    
                        vis[h] = true;
                        p[i] = h;    
                        LL tmp = G[n+1-i][T].down;
                    //    printf("tmp = %lld\n", tmp );    
                        if( tmp >= N )
                        {
                            clas = 0;    
                            break;    
                        }
                        else N -= tmp;    
                        vis[h] = false;            
                    }
            }
//            printf("p[%d] = %d, class = %d, N = %lld\n",i, p[i], clas, N );    
            
        }
        for(int i = 1; i <= n; i++ )
            printf( i == 1 ? "%d" : " %d", p[i] );    
        puts("");    
    }
    return 0;
}
原文地址:https://www.cnblogs.com/yefeng1627/p/2855259.html