1. 程式人生 > >BZOJ3591 最長上升子序列(狀壓dp)

BZOJ3591 最長上升子序列(狀壓dp)

[] line 以及 char == 最長上升子序列 lis stdin lib

  之前聽說過一種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[N],p[N+1
],f[15000000]; bool flag[1<<N][N]; int v[1<<N][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; }

BZOJ3591 最長上升子序列(狀壓dp)