poj1037 dp +排列计数


poj1037 dp +排列计数

今天学习dp  看的是北大培训的课件 看到了这道题  开始看的时候  就算知道是dp 也不知道怎么去写  后面看了ac代码


直接写思路  c[i][k][down]表示的是前i根木头中 以k打头阵的down总数

                    //c[i][k][up]表示的是 前i根木头中 以k打头阵的up总数

再写这道题时  首先要知道怎么去排列计数  直接列课件内容

                    如1,2,3,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计起)
      这种方法的核心在于求,给定前缀下后面总的排列数。全排列问题较易求。同时还需记录前面用过的数字

     直接看代码吧  代码有我自己的理解 

     后面的要归纳出怎么解dp的题  未完 

     

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int up=0,down=1;
const int maxn=25;
long long c[maxn][maxn][2];//c[i][k][down]表示的是前i根木头中 以k打头阵的down总数
						//c[i][k][up]表示的是 前i根木头中 以k打头阵的up总数
void init(int n)
{
	memset(c,0,sizeof(c));
	c[1][1][up]=c[1][1][down]=1;//n为1时  总数为1种 
	for(int i=2;i<=n;i++)//dp前i个 
	{
		for(int j=1;j<=i;j++)//枚举第一根 
		{
			for(int k=j;k<=i;k++)//枚举第二根 
				c[i][j][up]+=c[i-1][k][down];//这个表示j<k<i;表示比后面j大的木头打头阵的话
				//后面的转到j就是down因为后面的比j要打 
			for(int h=1;h<=j-1;h++)
				c[i][j][down]+=c[i-1][h][up];//这个就是从开始到j-1  比j小要转到j就要up 
		}
	}
}
void print(int n,long long cc)
{
	int used[maxn];
	int ans[maxn];//存储答案的 
	memset(used,0,sizeof(used));
	for(int i=1;i<=n;i++)
	{
		int no=0;//第no大的数 
		long long skip=0;//要跳过的数  就是排列的那个 
		int k;
		for(k=1;k<=n;k++)
		{
			if(!used[k])
			{
				no++;
				if(i==1)
					skip=c[n][no][down]+c[n][no][up];
				else {
					if(k>ans[i-1] && (i<=2 || ans[i-2]>ans[i-1]))//符合的排列
						skip=c[n-i+1][no][down];
					else if(k<ans[i-1] && (i<=2 || ans[i-2]<ans[i-1]))//符合的排列 
						skip=c[n-i+1][no][up];
				}
				if(skip>=cc)
					break;
				else
				 cc-=skip;
			}
		}
		used[k]=1;
		ans[i]=k;
	}
	for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	printf("\n");
}
int main()
{
	int t,n;
	long long c;
	init(20);
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%lld",&n,&c);
		print(n,c);
	}
	return 0;
} 

怎么解dp  怎么知道这是dp题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值