Description
给出一个长度为 m 的序列 A, 请你求出有多少种 1…n 的排列, 满足 A 是它的一个 LIS.
Input
第一行两个整数 n,m.
接下来一行 m 个整数, 表示 A.
Output
一行一个整数表示答案.
Sample Input
5 3
1 3 4
Sample Output
11
Data Constraint
对于前 30% 的数据, n ≤ 9;
对于前 60% 的数据, n ≤ 12;
对于 100% 的数据, 1 ≤ m ≤ n ≤ 15
Solution
因为n只有15左右,可以考虑状压DP
设f[s1][s2]表示选取s1的状态,而g中状态为s2(g[i]为长度为i的最小的结尾大小,g是递增的,在为1,不在为0)的方案数
显然可以整合成一个三进制状态s(0表示不选,1表示选但不在g中,2表示选且在g中(在g中的一定是被选过的))
对于当前状态s,它可以选取某个为0的位置(但是要保证a中的数是依次出现),然后更新状态。
因为可能有多种状态可以推至同一状态,所以按0的个数bfs处理,最后统计0的个数为0的状态且LIS为m的f[x]的和(可以计算x中2的个数)
时间复杂度为O(n*3^n),稍微需要卡常。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define M 15000000
#define N 20
int f[M],a[N],b[N],p[N],c[N],cnt[M];
int n,m,ans=0;
vector<int> t[N];
int main()
{
freopen("arg.in","r",stdin);
freopen("arg.out","w",stdout);
scanf("%d%d",&n,&m);
memset(b,0,sizeof(0));
p[1]=1;
for (int i=2;i<=n+1;i++) p[i]=p[i-1]*3;
for (int i=1;i<=m;i++)
{
scanf("%d",&a[i]);
b[a[i]]=i;
}
memset(cnt,0,sizeof(cnt));
for (int i=0;i<p[n+1];i++)
{
cnt[i]=cnt[i/3]+(i%3!=0);
t[n-cnt[i]].push_back(i);
}
memset(f,0,sizeof(f));
f[0]=1; c[n+1]=n+1;
int last,x,y,z,s;
for (int ii=n;ii>=0;ii--)
{
for (int j=1;j<=t[ii].size();j++)
{
if (f[t[ii][j]]<=0) continue;
last=0,x=t[ii][j],y,z,s=0;
for (int i=1;i<=m;i++)
if ((x/p[a[i]])%3==0) {y=i; break;}
for (int i=n;i>=1;i--)
if ((x/p[i])%3==2) c[i]=i;
else c[i]=c[i+1];
for (int i=1;i<=n;i++)
{
if (y>=b[i] && (x/p[i])%3==0)
{
z=x+2*p[i];
z-=p[c[i]]*(c[i]!=n+1);
f[z]+=f[x];
}
last+=((x/p[i])%3==2);
}
ans+=f[x]*(ii==0)*(last==m);
}
}
printf("%d\n",ans);
return 0;
}