动态规划 POJ 1037 A decorative fence

       这道题还是比较难想出来的,我也搞了好久才搞懂,而且是在问了大牛的基础上才搞清楚的。反而觉得黑书上没有讲清楚,现在我们来具体看看这道题的思路吧。

       我们这样看一个符合条件的序列,例如一个长度为n的序列,当我们去定了前面的k项的时候,后面剩下的n-k项与1到n-k的符合条件的序列有一个一一对应的关系。这便是我们使用动态规划的动机,存在重叠子问题。于是我们如下定义状态:

dp[i][j][0]表示以i为开头的,长度为j的上升的合法的序列的总数

dp[i][j][1]表示以i为开头的,长度为j的下降的合法的序列的总数

      我们当前的长度为j的序列的子问题正好与j-1长度的合法序列相对应(这一点需要仔细理解),于是可以确定我们状态的定义和转移时合法的。

dp[i][j][0]=sum{dp[x][j-1][1]} (j-1>=x>=i)

dp[i][j][1]=sum{dp[x][j-1][0]} (i>x>=1)

       我们预处理出来上面的dp值之后,就是构造解了。

       对于这种构造排列的问题,我们通常是逐位减去高位所确定的合法排列的个数。对于这道题我们也是一样,但是对于第一个起点我们要特殊处理,判断是上升还是下降。之后就直接减去符合条件的排列数,一次确定每一位就行了。

      最后输出解的时候,我们要注意,我们存的是在剩下的可选的数中的第几大的数,于是我们要反回来映射到1-n之间的数,反正数据量比较小,直接暴力枚举。

思路就是这样,不明白可以看看代码:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;
const int Max=25;
typedef long long LL;
LL dp[Max][Max][2];

void init(int n)
{
    memset(dp,0,sizeof(dp));
    dp[1][1][0]=1;//up
    dp[1][1][1]=1;//down
    for(int j=2;j<=n;j++)
    {
        for(int i=1;i<=j;i++)
        {
            for(int x=i;x<j;x++)
            {
                dp[i][j][0]+=dp[x][j-1][1];
            }
            for(int x=1;x<i;x++)
            {
                dp[i][j][1]+=dp[x][j-1][0];
            }
        }
    }
}
int ans[Max];
void solve(int n,LL c)
{
    int tn=n;
    int flag,cur;
    LL sum=0LL;
    for(int i=1;i<=n;i++)
    {
        if(sum+dp[i][n][0]+dp[i][n][1]>=c)
        {
            ans[1]=i;
            c-=sum;
            cur=i;
            break;
        }
        sum+=(dp[i][n][0]+dp[i][n][1]);
    }
    if(c<=dp[cur][n][1])  flag=1;
    else{c-=dp[cur][n][1];flag=0;}
    --n;
    int len=2;
    while(n>0)
    {
        if(flag==0)
        {
            for(int i=cur;i<=n;i++)
            {
                if(dp[i][n][1]>=c)
                {
                    cur=i;
                    ans[len++]=cur;
                    break;
                }
                c-=dp[i][n][1];
            }
        }
        else
        {
            for(int i=1;i<cur;i++)
            {
                if(dp[i][n][0]>=c)
                {
                    cur=i;
                    ans[len++]=cur;
                    break;
                }
                c-=dp[i][n][0];
            }
        }
        --n;
        flag=1-flag;
    }
    int vis[Max]={0};
    for(int i=1;i<=tn;i++)
    {
        for(int j=1;j<=tn;j++)
        {
            if(vis[j]==0)
            {
                ans[i]--;
                if(ans[i]==0)
                {
                    printf("%d%c",j,i==tn?'\n':' ');
                    vis[j]=1;
                    break;
                }
            }
        }
    }
}

int main()
{
    int T,n;
    LL c;
    scanf("%d",&T);
    init(20);
    while(T--)
    {
        scanf("%d %lld",&n,&c);
        solve(n,c);
    }
    return 0;
}


  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值