题目来源:http://poj.org/problem?id=1037
题目大意:
用长度从1至N的N块木板来围成一个围栏。要求是围栏成波浪形,即每块木板要么比它两边的木板都低要么比它两边的木板都高。现对所有符合要求的排列方式进行排序。排序规则是从第一块木板开始计算,越短的排名越前,前面的相等,向后依次比较。(即字典序)先给出N和一个指定的数字i,求符合要求的排列中的第i个。
输入:第一行一个正整数表示测试用例数。接下每行为一个测试用例,含两个数字分别表示N和i。
输出:指定的木板排列方案。
Sample Input
2 2 1 3 3
Sample Output
1 2 2 3 1
本题遍历显然是不可行的,会超时。解决思路是求给定前缀的排列有多少种,然后查表找出指定的序列。即求出以1开头的序列有多少种,以1开头的序列里第二个是2的有多少种...依次类推,建立起各种前缀排列数的表。求表的过程由DP实现。
所用的DP方法很巧妙。实际有两个dp表。一个up表,dp[len][i](up)表示len根木板的排列里,第一根木棍长度为i,第二根木棍比第一根长的方案数(M形)。同时还有一个dp[len][i](down)表示len根木板的排列里,第一根为i,第二根比第一根短的方案数(W形)。
dp[len][i].down = sigmaj(j<i)(dp[len-1][j].up);
dp[len][i].up=sigmaj(i<=j<=len-1)(dp[len-1][j].down).
对这两个式子的一个疑问是:当选定了i作为len长的序列的第一根木棍后,剩下的问题跟原问题不一样了,因为木棍不再是连续排列的。其实事实上,问题的本质还是一样的,我们可以把剩余的木棍中比i长的长度都减1。不过按这样计算出dp表后,在查表求指定序列时要注意把减去的长度复原。
1 ////////////////////////////////////////////////////////////////////////// 2 // POJ1037 A decorative fence 3 // Memory: 264K Time: 32MS 4 // Language: C++ Result: Accepted 5 ////////////////////////////////////////////////////////////////////////// 6 7 #include <iostream> 8 using namespace std; 9 10 struct Plan { 11 long long up; 12 long long down; 13 }; 14 15 int n; 16 long long c; 17 Plan dp[21][21]; 18 bool used[21]; 19 int index; 20 long long leftCnt; 21 22 void init() { 23 dp[1][1].up = dp[1][1].down = 1; 24 for (int len = 2; len <= 20; ++len) { 25 for (int j = 1; j <= len; ++j) { 26 for (int k = 1; k < j; ++k) { 27 dp[len][j].down += dp[len-1][k].up; 28 } 29 for (int k = j; k < len; ++k) { 30 dp[len][j].up += dp[len - 1][k].down; 31 } 32 } 33 } 34 } 35 int find(int k) { 36 int cnt = 0; 37 for (int i = 1; i <= n; ++i) { 38 if (used[i] == false) { 39 ++cnt; 40 } 41 if (cnt == k) { 42 return i; 43 } 44 } 45 } 46 47 void process() { 48 bool direction; 49 int first; 50 leftCnt = c; 51 memset(used, false, sizeof(used)); 52 for (first = 1; first <= n; ++first) { 53 if (dp[n][first].down >= leftCnt) { 54 direction = false;//down 55 break; 56 } 57 leftCnt -= dp[n][first].down; 58 if (dp[n][first].up >= leftCnt) { 59 direction = true;//up 60 break; 61 } 62 leftCnt -= dp[n][first].up; 63 } 64 used[first] = true; 65 cout << first; 66 int last = first; 67 for (int len = n - 1; len > 0; --len) { 68 int j; 69 if (direction == true) { 70 for (j = last; j < len; ++j) { 71 if (dp[len][j].down >= leftCnt) { 72 break; 73 } 74 leftCnt -= dp[len][j].down; 75 } 76 } else { 77 for (j = 1; j < last; ++j) { 78 if (dp[len][j].up >= leftCnt) { 79 break; 80 } 81 leftCnt -= dp[len][j].up; 82 } 83 } 84 int t = find(j); 85 cout << " " << t; 86 used[t] = true; 87 last = j; 88 direction = direction ? false : true; 89 } 90 cout << endl; 91 } 92 93 int main() { 94 int k; 95 cin >> k; 96 init(); 97 while (k--) { 98 cin >> n >> c; 99 process(); 100 } 101 system("pause"); 102 return 0; 103 }