zoukankan      html  css  js  c++  java
  • 【dp 状态压缩 单调栈】bzoj3591: 最长上升子序列

    奇妙的单调栈状压dp

    Description

    给出1~n的一个排列的一个最长上升子序列,求原排列可能的种类数。

    Input

    第一行一个整数n。
    第二行一个整数k,表示最长上升子序列的长度。
    第三行k个整数,表示这个最长上升子序列。

    Output

    第一行一个整数,表示原排列可能的种类数。

    Sample Input

    5
    3
    1 3 4

    Sample Output

    11

    HINT

    【样例说明】

    11种排列分别为(1, 3, 2, 5, 4), (1, 3, 5, 2, 4), (1, 3, 5, 4, 2), (1, 5, 3, 2, 4), (1, 5, 3, 4, 2), (2, 1, 3, 5, 4), (2, 1, 5, 3, 4), (2, 5, 1, 3, 4), (5, 1, 3, 2, 4), (5, 1, 3, 4, 2), (5, 2, 1, 3, 4)。

    【数据规模和约定】

    对于30%的数据,1 <= n <= 11。

    对于70%的数据,1 <= n <= 14。

    对于100%的数据,1 <= n <= 15,答案小于2^31。


    题目分析

    这种属于奇奇怪怪的状压dp:把单调栈拿来状压。

    考虑求解LIS,是一个使用单调栈的过程。因此按序列位置依次考虑,每一个数有三种状态:0:没有被考虑;1:考虑了,在栈里;2:考虑了,已出栈————自然可以三进制状压表示。

    再考虑dp的过程:用$f[i]$表示$i$这个三进制状态下的合法方案数。转移时则从小到大枚举每一个数,看其是否能够通过当前状态合法转移至下一个LIS(注意判断转移前的合法性)。

    转移不难,重点在于想到状压每个数对于单调栈的状态。

     1 #include<bits/stdc++.h>
     2 const int maxn = 23;
     3 
     4 int val[maxn],base[maxn],n,m;
     5 int num[maxn],pre[maxn];
     6 int LIS[maxn],cnt;
     7 int f[14349003],ans;      //f[]不要忘记开大,表示3^n种状态
     8 
     9 int read()
    10 {
    11     char ch = getchar();
    12     int num = 0;
    13     bool fl = 0;
    14     for (; !isdigit(ch); ch = getchar())
    15         if (ch=='-') fl = 1;
    16     for (; isdigit(ch); ch = getchar())
    17         num = (num<<1)+(num<<3)+ch-48;
    18     if (fl) num = -num;
    19     return num;
    20 }
    21 int main()
    22 {
    23     n = read(), m = read(), num[0] = 0;
    24     for (int i=1; i<=m; i++)
    25     {
    26         num[i] = read()-1, pre[num[i]] = i-1;
    27         if (num[i] < num[i-1]){
    28             puts("0");
    29             return 0;
    30         }
    31     }
    32     base[0] = 1;
    33     for (int i=1; i<=n; i++) base[i] = base[i-1]*3;
    34     f[0] = 1;
    35     for (int i=0; i<base[n]; i++)
    36         if (f[i]){
    37             int st = i,done,cnt,ins;
    38             done = cnt = ins = 0;
    39             for (int j=0; j<n; j++)
    40             {
    41                 val[j] = st%3, st /= 3;
    42                 if (val[j]) done++;
    43                 if (val[j]==1) LIS[cnt++] = j;
    44             }
    45             if (done==n){
    46                 ans += f[i];
    47                 continue;
    48             }
    49             for (int j=0; j<n; j++)
    50             {
    51                 if (val[j]) continue;
    52                 if (pre[j]&&!val[num[pre[j]]]) continue;
    53                 while (LIS[ins] < j&&ins < cnt) ins++;
    54                 if (ins==m) continue;
    55                 int nxt = i+base[j];
    56                 if (ins < cnt) nxt += base[LIS[ins]];
    57                 f[nxt] += f[i];
    58             }
    59         }
    60     printf("%d
    ",ans);
    61     return 0;
    62 }

    END

  • 相关阅读:
    LED点阵显示
    KEIL安装
    KEIL安装
    ubuntu安装svn
    python基础-面向过程编程
    js遇到代码出现问题时如何调试代码
    js内置对象的常用属性和方法(Array | String | Date | Math)
    js函数的使用+封装+代码复用
    JavaScript中条件分支语句和循环语句的使用,用简洁的代码实现强大功能
    JavaScript的语法、数据类型、基本算数和逻辑运算操作
  • 原文地址:https://www.cnblogs.com/antiquality/p/9373821.html
Copyright © 2011-2022 走看看