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题