jzoj3712 【NOI2014模拟6.30】石中剑的考验

xiaoxiao2021-02-28  97

题意

给出一个元素不重复,长为n<=15的序列的最长上升子序列之一。 求有多少个序列满足有这个最长上升子序列。

分析

这tm也是一题玄学题。

因为n=15,考虑状压,dp. 一个序列只要满足两个条件就是合法序列: 1)最长上升子序列长度为k,2)子序列存在于原序列中。

第二个好求,直接状压。

问题是第一个。 因为我们要求最长上升子序列的长度,所以状态里要带一个dp时的f数组 这样超时。 想一想求最长上升子序列的n log n算法,我们发现只需要存一个长度为i的上升子序列的最小结尾元素 也就是“辅助栈”就可以更新序列最长上升子序列的长度了。

但是怎么存? 因为辅助栈是单调的,所以只需要知道每个数有没有在辅助栈里出现就好了。 于是我们状态就有了,一个三进制的压位状态。 0表示没有出现过,1表示出现过,2表示出现过并且在辅助栈内。

转移比较显然,但需要加几个优化。 1)转移时注意到,因为x从小到大枚举,所以更新指针也会往前指。复杂度是O(n)的 2)找一下合法的一个必要条件优化: 因为a[i]是最长上升子序列中第i位,所以他前面不应该存在一个数x使得x<=a[i]且f[x]>=i.

#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define N 16 using namespace std; int f[14348908]; int n,k,a[N],g[N],app[N],t,su[N]; int m[N],ans,stm; void dfs(int x,int cnt,int st) { if (x>n) { if (f[st]==0) return; ++stm; g[0]=0; int s=st,nst=0,now=0; for (int i=1; i<=n; i++) { int yss=s/3,ys=s-yss*3; if (ys!=0) app[i]=stm; if (ys==2) g[++g[0]]=i; s=yss; if (s==0) break; } for (int x=1; x<=n; x++) if (app[x]!=stm) { if (su[x]>1 && app[a[ su[x]-1 ]]!=stm) continue; while (now<g[0] && g[now+1]<x) now++; bool bz=0; for (int i=1; i<=k; i++) if (app[a[i]]!=stm && a[i]>x && now+1>=i) { bz=1; break; } if (bz==1) continue; if (now==0 && g[1]<now) nst=st+m[x]; //0->1 else if (now!=g[0]) { nst=st+m[x]*2; //0->2 nst=nst - m[g[now+1]]; //2->1 } else nst=st+m[x]*2;//0->2 f[nst]+=f[st]; if (t==n-1 && g[0]+(now==g[0])==k) ans=ans+f[st]; } return; } if (n-x+1>t-cnt) dfs(x+1,cnt,st*3); if (cnt<t) { dfs(x+1,cnt+1,st*3+1); dfs(x+1,cnt+1,st*3+2); } } int main() { freopen("sword.in","r",stdin); freopen("sword.out","w",stdout); cin>>n>>k; m[1]=1; for (int i=2; i<=n; i++) m[i]=m[i-1]*3; for (int i=1; i<=k; i++) scanf("%d",&a[i]),su[a[i]]=i; if (k==1) { cout<<1<<endl; return 0; } f[0]=1; for (t=0; t<n; t++) dfs(1,0,0); cout<<ans<<endl; }
转载请注明原文地址: https://www.6miu.com/read-71018.html

最新回复(0)