版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/82948236
【问题描述】
给出一个长度为 m 的上升序列 A(1 ≤ A[i]≤ n) , 请你求出有多少种 1…n 的排列, 满足
A是它的一个 LIS.
【输入】
第一行两个整数 n,m.
接下来一行 m 个整数, 表示 A.
【输出】
一行一个整数表示答案.
【输入样例 1】
5 3
1 3 4
【输出样例 1】
11
【输入样例 2】
4 2
3 4
【输出样例 2】
5
【数据范围与约定】
对于前 30% 的数据, n ≤ 9;
对于前 60% 的数据, n ≤ 12;
对于 100% 的数据, 1 ≤ m ≤ n ≤ 15.
解析:
不要想爆搜剪枝,你剪不动的,不然试试下面两组数据。
15 7
1 3 5 7 9 11 13
15 7
9 10 11 12 13 14 15
反正就是各种卡,除非特判
然而你特判的完吗?
思路:
这道题正解是状压DP
数据范围只有15,这很状压。
首先要知道最快的求
的
级别的做法。
很容易想到用
表示目前选取的数的集合,
表示目前
的构成情况。
然而
的空间复杂度阻止了我们前进的脚步。。。
,然而这个时间复杂度有阻止了我们前进的脚步。。。
好吧这道题需要一些高超的状压技巧。三进制。。。
先看我们总的方案数,实际上所有的 都是 的自己,二项式展开一下得到 和 的综合状态只有 ,玄学三进制压一下就行了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
inline
ll getint(){
re ll num;
re char c;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return num;
}
cs int N=20;
cs int M=14348910;
int n,m;
int pow3[N],pow2[N];
int a[N];
ll f[M],ans;
inline
void dfs(int len,int dep,int v){
if(len>n){
if(!dep)ans+=f[v];
return ;
}
v+=pow3[len-1];
dfs(len+1,dep,v);
v+=pow3[len-1];
dfs(len+1,dep-1,v);
}
signed main(){
n=getint();
m=getint();
for(int re i=1;i<=m;++i)a[i]=getint();
pow2[0]=pow3[0]=1;
for(int re i=1;i<=n;++i)pow2[i]=pow2[i-1]<<1,pow3[i]=pow3[i-1]*3;
f[0]=1;
for(int re i=0;i<=pow2[n]-2;++i){
bool flag=1,tt=1;
for(int re j=1;j<=m;++j){
if((i&pow2[a[j]-1])==0)flag=false;
else if(!flag){
tt=false;
break;
}
}
if(!tt)continue;
for(int re subset=i;;subset=(subset-1)&i){
int now=0;
for(int re j=1;j<=n;++j){
if(subset&pow2[j-1])now+=pow3[j-1];
if(i&pow2[j-1])now+=pow3[j-1];
}
if(f[now]){
for(int re j=1;j<=n;++j){
if(now/pow3[j-1]%3==0){
int sta=now+2*pow3[j-1];
for(int re k=j+1;k<=n;++k)
if(now/pow3[k-1]%3==2){
sta-=pow3[k-1];
break;
}
f[sta]+=f[now];
}
}
}
if(!subset)break;
}
}
dfs(1,m,0);
cout<<ans;
return 0;
}