[bzoj3591]最长上升子序列——状压DP

题目大意:给出1~n的一个排列的一个长度为m的最长上升子序列,求原排列可能的种类数。

思路:考虑在 O(nlogn) O ( n l o g n ) 的最长上升子序列的求法中,我们建立了一个单调队列去DP,由于这个队列中的元素是单调的,所以可以用二进制去表示状态,第i位为1即为在队列中,0即不在队列中。
但是这样状压并没有考虑题目所给的限制:所给的序列为总序列的一个LIS,换一句话来说,就是这个总序列的LIS的长度必须为m,且所给的这个序列必须要是顺序加入单调队列中的。
于是我们可以转换为三进制来DP,每一个数多了一种状态,0表示未选入,1表示选入了但没有加入单调队列中,2表示选入了并且加入了单调队列中,为了保证我们状态是合法的,每次进行状态转移的时候要满足一下几个条件:

1.转移前后的序列的LIS长度都不可以超过m

2.如果新添加的这个数是所给的,那么它之前的数应该已经选中了

发现满足了这两个条件之后,由于2会保证题目所给的数是顺次加入的,1会保证LIS的长度不会超过m,所以最后合法的状态的LIS的长度一定是等于m
考虑如何DP,枚举每一个序列,并且枚举这个序列的每一个子集,所表示的意义即为在单调队列中的元素,然后将这两个状态转化为3进制之后加起来就可以枚举新的要添加的数来转移了

看代码也许更好理解

/*========================
 * Author : ylsoi
 * Problem : bzoj3591
 * File : bzoj3591.cpp
 * Algorithm : DP
 * Time : 2018.3.28
 * =====================*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
void File(){
#ifndef ONLINE_JUDGE
    freopen("bzoj3591.in","r",stdin);
    freopen("bzoj3591.out","w",stdout);
#endif
}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define ll long long
template<typename T>void chckmax(T &_,T __){_=_>__ ? _ : __;}
template<typename T>void chckmin(T &_,T __){_=_<__ ? _ : __;}
const int maxn=20;
const int maxs3=3*3*3*3*3*3*3*3*3*3*3*3*3*3*3+10;
const int maxs2=(1<<15)+10;
int n,m,a[maxn],id[maxn],pow3[maxn],dp[maxs3],ans;
int b[maxs2],to[maxs2][maxn],pow2[maxn];
void out(int x,int len){
    if(!len)return;
    out(x/3,len-1);
    cout<<x%3;
}
int main(){
    File();
    scanf("%d%d",&n,&m);
    memset(id,-1,sizeof(id));
    REP(i,0,m-1){
        scanf("%d",&a[i]);
        --a[i];
        id[a[i]]=i;
    }
    pow3[0]=pow2[0]=1;
    REP(i,1,15)pow3[i]=pow3[i-1]*3;
    REP(i,1,15)pow2[i]=pow2[i-1]*2;
    int end=pow2[n]-1;
    REP(S,0,end)REP(i,0,n-1)if((pow2[i])&S)b[S]+=pow3[i];
    REP(S,0,end-1){
        if(__builtin_popcount(S)>m){
            to[S][n]=1;
            continue;
        }
        REP(i,0,n-1){
            if(pow2[i]&S)continue;
            int pos=i+1;
            while(pos<n && (!((pow2[pos])&S)))++pos;
            int SS=S|(pow2[i]);
            if((pow2[pos])&S)SS^=(pow2[pos]);
            if(__builtin_popcount(SS)>m)continue;
            to[S][i]=SS;
        }
    }
    dp[0]=1;
    REP(S,0,end-1){
        for(int S0=S;;S0=(S0-1)&S){
            if(dp[b[S]+b[S0]]){
                if(to[S0][n])continue;
                REP(i,0,n-1){
                    if((pow2[i])&S)continue;
                    if(!to[S0][i])continue;
                    if(id[i]>0 && (!((pow2[a[id[i]-1]])&S)))continue;
                    int S2=to[S0][i];
                    dp[b[S2]+b[S]+pow3[i]]+=dp[b[S]+b[S0]];
                    if(__builtin_popcount(S)==n-1){
                        ans+=dp[b[S]+b[S0]];
                        cout<<__builtin_popcount(S2)<<endl;
                    }
                }
            }
            if(!S0)break;
        }
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值