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
考虑计算 LIS 时使用的经典方法。
记一个数组 f ,
f[i] 表示当前长度为 i 的单调上升序列的结尾的最小值。这个数组是一个单调不降的数组. 于是我们可以用二进制来表示它。
设
f(S,S0) 表示当前选了集合 S 里的数,LIS 的f 数组状态为 S0 时的方案数。这不难转移,枚举在末尾加上哪个数即可。
观察发现 S0 一定属于 S ,所以可以压成三进制。
每位用
0,1,2 分别表示还没选、选了且在单调上升序列、选了但不在单调上升序列。于是枚举状态,算出目前的单调上升序列,在枚举还没选的转移即可。
注意枚举转移的数要按照 A 序列的顺序来。
若枚举到某一状态,其中每个数都已经选了,且单调上升序列的长度就是 m ,即可统计答案。
时间复杂度
O(3N∗N) ,实际小很多,因为很多状态都遍历不到。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
int n,m,ans;
int a[17],b[17],c[17],p[17],f[14348907+5];
bool bz[17];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
b[i]=read();
if(b[i]<=b[i-1] || b[i]<1 || b[i]>n) return 0&printf("0");
}
for(int i=p[0]=1;i<=n;i++) p[i]=p[i-1]*3;
f[0]=1;
for(int i=0;i<p[n];i++)
if(f[i])
{
bool pd=true;
int tot=0,x=i;
for(int j=n-1;j>=0;j--)
{
c[j+1]=x/p[j];
if(x>=p[j])
{
if(c[j+1]==1) a[++tot]=j+1;
x%=p[j];
}else pd=false;
}
reverse(a+1,a+1+tot);
if(pd && tot==m) ans+=f[i];
memset(bz,false,sizeof(bz));
int num=1;
while(num<=m && c[b[num]]) num++;
while(num+1<=m) bz[b[++num]]=true;
for(int j=1;j<=n;j++)
if(!c[j] && !bz[j])
{
if(j>a[tot])
{
if(tot==m) continue;
f[i+p[j-1]]+=f[i];
}else
{
int y=upper_bound(a+1,a+1+tot,j)-a;
f[i+p[j-1]+p[a[y]-1]]+=f[i];
}
}
}
printf("%d",ans);
return 0;
}