[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
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个 $n$。 接下来 $n$ 行,每行 $n$ 个整,表示棋盘上每个点的字。 输出格式 输出一个,表示所有满足条件的路径中,所有点的权值和的最小值。 据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值