【刷题】BZOJ 3591 最长上升子序列

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。

Solution

一道dp题

我们先考虑对一个数列求LIS的方法(\(log\) 的那个),一个 \(lis\) 数组,\(lis[i]\) 记录长度为 \(i\) 的LIS的末尾最小可以是多少

对于 \(lis\) 整个数组,不难发现它是单调递增的,所以我们可以用二进制表示它,一样是01表示某个数是否出现在 \(lis\) 数组中,因为递增,所以我们这要知道有哪些数在里面,就可以还原出原来的 \(lis\) 数组

然后考虑本题的dp,我们用一个 \(f\) ,考虑对于当前的LIS,插入新数的情况

所以要保存每个数的三个状态

一是这个数还没有被考虑,二是这个数已被考虑,并且在LIS数组中出现,三是这个数已经被考虑,并且已经被弹出LIS数组

所以用三进制压位,012分别代表上面三个状态

转移时,枚举每一个LIS的情况,然后枚举每一个数,插入进去,再把当前的状态转移到插入后的状态

这种dp题看代码更好理解

#include<bits/stdc++.h>
#define ll long long
#define db double
#define ld long double
const int MAXN=16,MAXN_3=14348907+10;
int A[MAXN],n,m,pos[MAXN],lis[MAXN],vis[MAXN],val[MAXN],f[MAXN_3],ans,qexp3[MAXN];
template<typename T> inline void read(T &x)
{
    T data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
    x=data*w;
}
template<typename T> inline void write(T x,char c='\0')
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
    if(c!='\0')putchar(c);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
int main()
{
    read(n);read(m);
    for(register int i=1;i<=m;++i)
    {
        read(A[i]);
        A[i]--;
        pos[A[i]]=i;
        if(i>=2&&A[i-1]>A[i])
        {
            write(0,'\n');
            return 0;
        }
    }
    qexp3[0]=1;
    for(register int i=1;i<=n;++i)qexp3[i]=qexp3[i-1]*3;
    f[0]=1;
    for(register int i=0;i<qexp3[n];++i)
    {
        if(!f[i])continue;
        int x=i,lislen=0,app=0;
        for(register int j=0;j<n;++j)
        {
            val[j]=vis[j]=x%3;
            x/=3;
            if(vis[j])app++;
            if(val[j]==1)lis[lislen++]=j;
        }
        if(app==n)
        {
            ans+=f[i];
            continue;
        }
        int ins=0;
        for(register int j=0;j<n;++j)
        {
            if(vis[j])continue;
            if(pos[j]>1&&!vis[A[pos[j]-1]])continue;
            while(ins<lislen&&lis[ins]<j)ins++;
            if(ins==m)continue;
            int nxt=i+qexp3[j];
            if(ins<lislen)nxt+=qexp3[lis[ins]];
            f[nxt]+=f[i];
        }
    }
    write(ans,'\n');
    return 0;
}

转载于:https://www.cnblogs.com/hongyj/p/8652928.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值