【BZOJ 3591】 最长上升子序列

哎我还是太菜了,这么短的代码 断断续续 调了好久。

看数据范围 大概就是状压 DP

题目名字是【最长上升子序列】 可以猜测是拿跑 LIS 的单调栈搞搞。

那么可以设计一个状态 f[A][B] 表示已选集合 A ,其中 B 是当前单调栈里的元素,显然栈里的元素升序排列,不用额外加一维状态表示栈中元素的排列。显然这可以优化成 3n 的状态表示。

转移大概就是枚举 A 补集中的每个元素 x 加到单调栈中某个位置,两边的元素保留。由于题目已经给出一个必选的 LIS( 记为 C) ,并且 DP 做的最后的时候所有元素都会被加进来,所以我们只要在 DP 过程中排除掉非法情况就可以了,这里会产生非法情况当且仅当 x 属于 C 且加入 x 的时候跳过了另一个属于 C 的元素,也就是在 C 中, x 的前一个元素未被加入已选集合 A 。可以预处理出 g[i][j] 表示已选集合 i 加入元素 j 是否合法。

还有一个不知道有多大用 的常数优化。就是限制一下 B 的大小使其不超过 C 的大小。

#include 
using namespace std;
const int N=15;
int n,m,S,T,pre[N],bit[1<],pow3[N],g[1<][N],d[1<][N],f[14348908],h[1<<N];
int main(){
    //freopen("aa.in","r",stdin);
    int i,j,k,x,y;
    scanf("%d%d",&n,&m);
    for(y=-1,i=0;i
        scanf("%d",&x);--x;
        T|=(1<
    }
    for(pow3[0]=i=1;i
    S=(1<
    for(i=0;i<=S;++i)bit[i]=bit[i>>1]+(i&1);
    for(i=0;i<=S;++i)for(j=0;j
    for(i=0;i<=S;++i)for(j=0;j
        if(!((1<
        else{
           for(k=pre[j];~k;k=pre[k])if(!(1<
           if(k<0)g[i][j]=1;
        }
        if(bit[((1<<(j+1))-1)&i]
           for(k=1<<(j+1);k<=i&&!(k&i);k<<=1);
           d[i][j]=1<
           if(k<=i)d[i][j]^=k; 
        }
    }
    f[0]=1;
    for(i=0;i<=S;++i)for(k=0;k
        for(j=i;;j=(j-1)&i){
            if(f[h[i]+h[j]]&&d[j][k])f[h[1<
            if(!j)break;
        }
    }
    int ans=0;
    for(i=1;i<=S;++i)if(bit[i]<=m)ans+=f[h[S]+h[i]];
    printf("%d",ans);
    return 0;
}

你可能感兴趣的:(BZOJ)