题目
给出一个长度为 m 的序列 A, 请你求出有多少种 1…n 的排列, 满足 A 是它的一个 LIS.
对于 100% 的数据, 1≤m≤n≤151≤m≤n≤15.
解题思路
对于n≤12n≤12的,其实可以暴力,将能想到的剪枝想到。
①由于n≤12n≤12,可以通过O(n2)O(n2)的暴力将f[i]f[i]构造出来。很容易得到,在递归中假设现在递归地层数为xx,那么已知。如果f[x]>mf[x]>m,那么return。
②考虑反数组即o[a[i]]=io[a[i]]=i.相当于新构造出来的序列中AA的元素在新构造出来的序列中出现的顺序是不变的,维护一个指针,如果加入的新数跳过了本来应在它前面加的数,那么return。
③如果当前LIS的长度加上剩下的数的个数还小于m,则return。
如果?
考虑有什么方法能够构造出LIS。考虑实时得出某个序列的办法。可以考虑用进制数作为状态的DP。
设g[i]g[i]表示当前的上升子序列的长度为i的末尾那个数的最小值。
考虑操作一步对序列的影响。
那么新加入进来的数i必定替代刚好比i大的j。
比如1 3 4 5,加入2,则新序列变成1 2 4 5。
那么一个数有3种状态。0:没出现过。1:在序列中。2:被踢了。
显然对于现在的一个三进制状态ss,可以转换到状态。
BFS的注意事项
在BFS中,如果一个状态要更新其他状态,那么必须要保证它已经被更新完了。
在这里,如果i++地枚举,那么可以满足上述要求。
如果将图建出来,不仅要花很大的空间,而且不一定能满足上述要求。
自己试一试就知道了。
心得
对于n比较小的。数的状态有几个,可以考虑几进制的状态数。
碰到这种关于BFS的DP问题,考虑用什么方法不仅将图遍历,且满足DP的合法的更新状态的方法。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 20
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,n,m,x,y,z,wz,ans;
int mx,lim;
int a[N],b[N],f[20000000],o[N];
int _3[N],c[N];
bool p;
int main(){
freopen("arg.in","r",stdin);
freopen("arg.out","w",stdout);
_3[1]=1;fo(i,2,17)_3[i]=_3[i-1]*3;
scanf("%d%d",&n,&m);
fo(i,1,m)scanf("%d",&a[i]),c[a[i]]=i;
f[0]=1;
fo(x,0,_3[n+1]-1)if(f[x]){
fo(i,1,n)o[i]=(x/_3[i])%3;
wz=1;b[0]=0;
fo(i,1,n)if(o[i]==1)b[++b[0]]=i;
mx=0;
fo(i,1,n)if(o[i])mx=max(mx,c[i]);
fo(i,1,n)if(!o[i]){
while(i>b[wz]&&wz<=b[0])wz++;
if(c[i]&&c[i]!=mx+1)continue;
if(i<b[wz]&&wz<=b[0])f[x+_3[b[wz]]+_3[i]]+=f[x];
else f[x+_3[i]]+=f[x];
}
}
lim=_3[n+1]-1;
fo(i,1,lim){
fo(j,1,n)o[j]=(i/_3[j])%3;
p=0;
int c1=0,c2=0;
if(!p)fo(j,1,m)if(!o[a[j]]){p=1;break;}
if(!p){
fo(j,1,n)if(o[j]==1)c1++;
else if(o[j]==2)c2++;
if(c1==m&&c1+c2==n)ans+=f[i];
}
}
printf("%d",ans);
return 0;
}