NOIP模拟 上升序列
【题目描述】
给出一个长度为 m 的上升序列 A(1 ≤ A[i]≤ n), 请你求出有多少种 1...n 的排列, 满足 A 是它的一个 LIS.
【输入格式】
第一行两个整数 n,m.
接下来一行 m 个整数, 表示 A.
【输出格式】
一行一个整数表示答案.
【样例输入】
5 3
1 3 4
【输出格式】
11
【备注】
对于前 30% 的数据, n ≤ 9;
对于前 60% 的数据, n ≤ 12;
对于 100% 的数据, 1 ≤ m ≤ n ≤ 15.
【题目分析】
一看这个数据范围,觉得像状压DP,但打死想不出压什么,打了个深搜。。。滚粗。。。
最长不降子序列的一种求法是:使用附加数组D[i],表示当前长度为 i的最长不降子序列最小的结尾是多少,显然它是单调递增的。每插入一个数就二分找到第一个大于它的位置替换。
我们可以设状态S1,第i位为 1表示它在D 中出现了,出现的位置就是1~i位1 的数量
现在就可以设递推状态f(S,S1)表示当前用了S中的数,D的状态为S1的方案数
转移很容易转移,枚举这一位选什么直接更新
注意到S1一定是S的子集,于是可以用三进制表示
复杂度O(3^n∗ n)实际上远远不到
【代码~】
#include<bits/stdc++.h> #define fo(i,a,b) for(int i=a;i<=b;++i) #define fod(i,a,b) for(int i=a;i>=b;--i) #define N 16 #define M 14348910 #define LL long long using namespace std; int n,m,cf[N],a[N],wz[N],cf2[N]; LL f[M],ans; void dfs(int k,int lim,int v) { if(k>n) { if(lim==0) ans+=f[v]; return; } v+=cf[k-1]; dfs(k+1,lim,v); v+=cf[k-1]; dfs(k+1,lim-1,v); } int main() { cin>>n>>m; fo(i,1,m) scanf("%d",&a[i]),wz[a[i]]=i; cf[0]=cf2[0]=1; fo(i,1,n) cf[i]=cf[i-1]*3,cf2[i]=cf2[i-1]*2; f[0]=1; fo(i,0,cf2[n]-2) { bool pd=1,c1=1; fo(j,1,m) { if((i&cf2[a[j]-1])==0) pd=0; else if(!pd) { c1=0; break; } } if(c1) { for(int i1=i;1;i1=(i1-1)&i) { int vi=0; fo(j,1,n) { if(i1&cf2[j-1]) vi+=cf[j-1]; if(i&cf2[j-1]) vi+=cf[j-1]; } if(f[vi]) fo(j,1,n) { if(vi/cf[j-1]%3==0) { int v=vi+2*cf[j-1]; fo(p,j+1,n) if(v/cf[p-1]%3==2) { v-=cf[p-1]; break; } f[v]+=f[vi]; } } if(i1==0) break; } } } dfs(1,m,0); printf("%lld\n",ans); return 0; }