您的位置:首页 > 其它

BZOJ3591 最长上升子序列(状压dp)

2018-09-26 13:03 232 查看

  之前听说过一种dp套dp的trick,大致是用另一个dp过程中用到的一些东西作为该dp的状态。这个题比较类似。

  考虑求LIS时用到的单调队列。设f[S]为所选取集合为S的方案数,其中在单调队列内的标2不在的标1。转移时考虑选择一个数是否合法,这只需要保证LIS长度不超过k且所给数的相对顺序不变。

  注意dp顺序,从小到大先枚举选了哪些数再枚举哪些在单调队列里。以及注意卡常。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
#define N 15
int n,m,id
,p[N+1],f[15000000];
bool flag[1<<N]
;
int v[1<<N]
,c[1<<N],mx[1<<N],s[1<<N],ans=0;
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj3591.in","r",stdin);
freopen("bzoj3591.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read(),m=read();
for (int i=1;i<=m;i++) id[read()-1]=i;
p[0]=1;for (int i=1;i<=n;i++) p[i]=p[i-1]*3;
for (int i=0;i<(1<<n);i++)
for (int j=0;j<n;j++)
if (!(i&(1<<j)))
{
if (!id[j]) flag[i][j]=1;
else
{
int tot=0;
for (int k=0;k<n;k++)
if ((i&(1<<k))) tot+=(id[k]>0);
if (tot+1==id[j]) flag[i][j]=1;
}
int t=-1,x=0;
for (int k=0;k<n;k++)
if (i&(1<<k)) x+=p[k];
for (int k=n-1;k>j;k--)
if (i&(1<<k)) t=k;
if (~t) v[i][j]=x-p[t]+p[j];
else v[i][j]=x+p[j];
}
else c[i]+=p[j],mx[i]=max(mx[i],j),s[i]++;
f[0]=1;
for (int x=0;x<(1<<n);x++)
for (int y=x;y>0||y==0&&x==0;x==0?y--:y=y-1&x)
if (f[c[x]+c[y]])
{
int i=c[x]+c[y],z=c[x];
for (int j=0;s[y]==m?j<mx[y]:j<n;j++)
if (flag[x][j]) f[z+p[j]+v[y][j]]+=f[i];
if (x==(1<<n)-1) ans+=f[i];
}
cout<<ans;
return 0;
}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: