NOIP模拟 上升序列(最长上升子序列+状压DP)
【题目描述】
给出一个长度为 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;
}