[bzoj3591][状压DP]最长上升子序列

123 篇文章 1 订阅

Description

给出1~n的一个排列的一个最长上升子序列,求原排列可能的种类数。

Input

第一行一个整数n。 第二行一个整数k,表示最长上升子序列的长度。 第三行k个整数,表示这个最长上升子序列。

Output

第一行一个整数,表示原排列可能的种类数。

Sample Input

5

3

1 3 4

Sample Output

11

HINT

【样例说明】

11种排列分别为(1, 3, 2, 5, 4), (1, 3, 5, 2, 4), (1, 3, 5, 4, 2), (1, 5, 3,
2, 4), (1, 5, 3, 4, 2), (2, 1, 3, 5, 4), (2, 1, 5, 3, 4), (2, 5, 1, 3,
4), (5, 1, 3, 2, 4), (5, 1, 3, 4, 2), (5, 2, 1, 3, 4)。

【数据规模和约定】

对于30%的数据,1 <= n <= 11。

对于70%的数据,1 <= n <= 14。

对于100%的数据,1 <= n <= 15,答案小于2^31。

题解

仙气缭绕…
考虑LIS的单调队列做法
每次加数的时候找到第一个比他大位置替换掉它
考虑模拟一个类似这样的过程
f [ i ] [ j ] f[i][j] f[i][j]表示没有加入数的状态是 i i i,加入队列的数的状态是 j j j
显然可以用三进制压
转为 f [ i ] f[i] f[i]表示当前数的状态是 i i i
0代表没有被加入 1代表当前这个数在LIS的队列中 2代表这个数被踢出了队
每次暴力加数
合法的状态很少可以直接杠掉…
写了注释应该很好懂…

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,pa[20];
int tp[20],tlen;
void get(int x)
{
	memset(tp,0,sizeof(tp));tlen=0;
	while(x)tp[++tlen]=x%3,x/=3;
}
bool isfir[20];
int f[14358907],bin[25],li[20],tap;
//0 没有出现过 1 在队列里 2 出队了 
int main()
{
	bin[1]=1;for(int i=2;i<=17;i++)bin[i]=bin[i-1]*3;
	scanf("%d%d",&n,&m);
	memset(isfir,false,sizeof(isfir));
	for(int i=1;i<=m;i++)scanf("%d",&pa[i]),isfir[pa[i]]=true;
	f[0]=1;int mx=1;
	for(int i=1;i<=n;i++)mx*=3;
	mx--;
	int ans=0,gg;
	for(int i=0;i<=mx;i++)
		if(f[i])
		{
			tap=gg=0;get(i);
			for(int j=1;j<=n;j++)
			{
				if(tp[j]==1)li[++tap]=j;
				if(tp[j])gg++;		
			}
			/*printf("CHECKER: %d    ",i);
			for(int j=n;j>=1;j--)printf("%d ",tp[j]);
			puts("");*/
			int ct=0,u=0;//当前还没有入队的原LIS状态有多少个 
			for(int j=1;j<=n;j++)
			{
				if(!tp[j])
				{
					if(isfir[j]&&ct)continue;
					if(isfir[j])ct++;
					while(u<tap&&li[u+1]<j)u++;
					if(u==tap&&tap<m)f[i+bin[j]]+=f[i];
					else if(u==tap&&tap>=m)continue;
					else f[i+bin[li[u+1]]+bin[j]]+=f[i];
				}
			}
			if(gg==n)ans+=f[i];
		}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值