POJ1037 A decorative fence

题目来源: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 }
View Code
posted @ 2013-08-01 21:22  小菜刷题史  阅读(267)  评论(0编辑  收藏  举报