我好菜啊……好菜啊……好菜啊……
上升序列
描述
给出一个长度为 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.
牢骚写在前面
大家最好还是不要用next_permutation()这个函数来求全排列,特别是当你是用暴力在做这道题的时候。
这个东西来的简单,分数也就来得少了
因为很多时候暴力是需要剪枝的,而当你用next_permutation的时候就完全不可能剪枝了,人家cxr大佬暴搜+剪枝 70分,我呢,用个函数用掉了40分
分析
这个数据范围很状压,但我依旧没有看出来,为什么那么弱啊%>_<%(我好菜啊……好菜啊……好菜啊……)
但即使知道是状压,也没搞出来
后面在ldx大佬的耐心讲解下,总算是会了,%%%%%%%%广告%%%%%%%%%
先知道最长不降子序列的一种求法是(就是那个优秀算法 nlogn):使用附加数组D[i],表示当前长度为 i 的最长不降子序列最小的结尾是多少,显然它是单调递增的。每插入一个数就二分找到第一个大于它的位置替换。
我们可以设状态S1,第i位为 1表示它在D 中出现了,出现的位置就是1~i位 1 的数量
现在就可以设递推状态 f (S,S1) 表示当前用了S中的数(就是1~n),D的状态为S1的方案数
转移很容易转移,枚举这一位选什么直接更新
但如果简单这样搞的话,时间和空间限制都会受不了啊,因此我们再观察一下,注意到S1一定是S的子集,因为要在D中出现,那么这个数肯定被 S 选了,于是可以用三进制表示
然后我就懵了,什么三进制表示????
好吧,其实就是这样的,
0表示这一位的数既没有在S中出现,又没有在S1中出现
1表示在S中出现了或者在S1中出现了
2表示既在S中又在S1中
然后就乱搞了……看代码吧,虽然ldx大佬diss我谁还会加注释啊,但我依然要加,╭(╯^╰)╮
代码
#include <bits/stdc++.h>
#define ll long long//注意一下范围咯
using namespace std;
int n,m,a[20],s1[20],s2[20];
ll f[14348990],ans=0;//f数组的大小 3^15
bool check(int sta){//a数组的数必须是有顺序有先后关系的出现
int flag=0;
for(int i=1;i<=m;++i){
if((sta&s1[a[i]-1])==0) flag=1;
else if(flag) return false ;
}
return true;
}
void dfs(int pos,int use,int sta){//统计答案,选了n个数,且LIS的长度为m
if(pos==n+1){if(!use) ans+=f[sta];return;}
sta+=s2[pos-1];dfs(pos+1,use,sta);
sta+=s2[pos-1];dfs(pos+1,use-1,sta);
}
int main(){
int i,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i) scanf("%d",&a[i]);
s1[0]=s2[0]=1;
for(i=1;i<=15;++i) s1[i]=s1[i-1]*2,s2[i]=s2[i-1]*3;
f[0]=1;//dp问题的初始值
for(i=0;i<s1[n];++i){//枚举S的所有状态
if(!check(i)) continue;
int sub=i;
for(int sub=i;1;sub=(sub-1)&i){//枚举S1,因为S1是S的子集嘛
//这个枚举方法一定要学到
int sta=0;//现在就根据S和S1推出三进制的表示
for(int j=1;j<=n;++j){
if(i&s1[j-1]) sta+=s2[j-1];
if(sub&s1[j-1]) sta+=s2[j-1];
}
if(f[sta]){//往下转移
for(int j=1;j<=n;++j){
int msta=sta;
if(msta/s2[j-1]%3==0){//如果这个数没有被使用过
msta+=2*s2[j-1];
for(int k=j+1;k<=n;++k) if(msta/s2[k-1]%3==2) {msta-=s2[k-1];break; }//更新d数组,不清楚的同学可以参见我之前的博客
f[msta]+=f[sta];//累计
}
}
}
if(!sub) break;
}
}
dfs(1,m,0);
cout<<ans;
return 0;
}