POJ 1037 计数DP难题。
这个题真正的难点并不在求出总的组合数,而在于如何将解还原出来。首先我们建立状态转移方程。我们定义 高低高低高 为0型,定义 地高低高低高 为1型,假设S[i]为长度为i的可行方案的全部升序数列集合。S[i][j]是开头第j大的可行方案的全部数列集合
dp[i][j][0] := i长度,第j大的作为开头的0型数量,同理1型。
可以得到状态转移方程:
dp[i+1][j][1] = Sum(dp[i][k][0]) j <= k <= i
dp[i+1][j][0] = Sum(dp[i][k][1]) 1 <= k < j
解释状态转移方程的意义,首先,1型可以看作是第j高的之后拼上比第j高更高的0型,比第j高更高的0型的起始高度是第j-i,原因是,想象1234,当第二个,也就是2取走后,134当中第2个到最后是比2高的。同理0型。
统计出了各种长度的篱笆不同的起始高度的数量,下一步便是还原出原有的篱笆。
考虑求出 1 2 3 4的第7大字典序排列方式这一问题。
这一问题的解法是,假设始选了1,之后包含了3!=6种方案。6小于7,我们跳过这6种比不是答案的方案,假设选了2,则跳过了12种方案,大于7,所以第一个数是2,当前跳过了6个方案,数列剩下:1,3,4.假设下一个选择了1,则跳过了 7 + 2!=9种方案,大于6,选择1,当前跳过的方案数是6,同样道理,下一个选择3,再下一个选4,2134就是答案。
我们采用相同的策略,枚举当前未使用过的木棍,叠加计数当前跳过的方案,如果大于输入的方案,则立刻选择。只不过是这里的叠加策略需要讨论当前生成篱笆的几何结构。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int N;
ll C;
ll dp[21][21][2];
bool used[21];
int res[21];
int main(){
int Num;
scanf("%d", &Num);
dp[1][1][1] = dp[1][1][0] = 1;
for(int i = 2; i <= 20; i ++){
for(int j = 1; j<= i; j ++){
for(int k = j; k <= i; k ++){
dp[i][j][1] += dp[i - 1][k][0];
}
for(int k = j - 1; k >= 1; k --){
dp[i][j][0] += dp[i - 1][k][1];
}
}
}
for(int num =0; num < Num; num ++){
scanf("%d %lld", &N, &C);
memset(used, 0, sizeof(used));
ll skipped = 0;
for(int i = 1; i <= N; i ++){
ll old_val = skipped;
int No = 0;
for(int j = 1; j <= N; j ++){
old_val = skipped;
if(!used[j]){
No ++;
if(i == 1){
skipped += (dp[N][No][0] + dp[N][No][1]);
} else {
if(j > res[i - 1] && (i <= 2 || res[i - 2] > res[i - 1])){
skipped += dp[N - i + 1][No][0];
}
if(j < res[i - 1] && (i <= 2 || res[i - 2] < res[i - 1])){
skipped += dp[N - i + 1][No][1];
}
}
if(skipped >= C){
res[i] = j;
used[j] = true;
skipped = old_val;
break;
}
}
}
}
for(int i = 1; i <= N; i ++){
printf("%d ", res[i]);
}
printf("\n");
}
}