51 nod 1775 LIS Counting


基准时间限制:1.5 秒 空间限制:262144 KB 分值: 640  难度:8级算法题

输入一个数列a[1..n],其中1<=a[i]<=n,且a[i]≠a[j] (i≠j),也就是说a是1~n的一个排列。

a的子序列是从a中删除若干个元素后,剩下的元素按照原来顺序组成的序列。显然,a有2^n个子序列(包括空序列以及a本身)。

一个序列b的最长上升子序列(LIS),定义为b的最长的满足元素单调递增的子序列。我们用LIS(b)来表示b的最长上升子序列的长度。

现在,你需要对于k=0,1,...,n,求出a有多少个子序列b,满足LIS(b)==k。
Input
第一行一个整数n (1<=n<=35)。
第二行是空格隔开的a[1],a[2],...,a[n]。
Output
输出n+1行,分别为k=0,1,2,...,n时的答案。
Input示例
5
1 5 2 4 3
Output示例
1
10
15
6
0
0

题解:

首先必须要晓得利用单调栈求LIS的经典做法(不会的自己百度) 
而此题就是基于单调栈实现的 ,可以考虑从前往后讨论每一个字符加入还是不加人 , 这样便可以得到母串的所有子序列,而对于所有的子序列都要求得LIS显然是不可能的,
其实通过分析可以知道当前第i个字符,不管加不加入 ,之前的前i-1个字符的LIS是单调不减的 ,所以可以考虑利用之前的状态通过转移直接得到后面的状态。这很显然是个
dp ,对于代码中的两个函数Choose , Not_Choose , 分别为当前字符加入和不加人 , 为什么要Not_Choose ,因为用的是滚动数组 , 所以必须将前面的所有状态进行转移
由于35对于状态压缩来说还是比较大了,而LIS并不强调值本身, 值需要大小关系, 所以二进制位i+1~35并不代表i+1 ~ 35 是否在栈中, 而是表示比i大的有k个(k表示i+1 ~35 有1的位数)
而Not_choose 使得sta发生变化 , 但本质值与值之间大小关系并没有变化。

如果没有看懂的 可以结合官方题解在进行理解:


求LIS的经典做法是维护一个单调栈,然后从左到右扫,添加一个新元素时,找到栈中第一个比它大的元素,并用这个元素替换;如果找不到,则加到栈顶。最后栈的元素个数就是LIS长度。
现在我们要DP这个东西。一个自然的想法是把这个栈的状态压成一个n位01串s。dp状态就是f[i][s]表示前i个数中取了若干,栈中的情况是s的方案数。
然而n是35,直接这么搞的话状态数比较大。需要优化。
观察一下转移的过程,做到第i个数时,将来会被插入的数字只有a[i+1],a[i+2],...,a[n],因此将来的转移只和栈中数字与这些数字的大小关系有关。把a[i+1..n]的数字排序后设为b[1]<b[2]<..<b[n-i],那么dp状态实际上只需要记录栈中的数字在区间(0,b[1]),(b[1],b[2]),(b[2],b[3]),...里分别有几个,而不需要具体记录是哪些数字,因为目前在同一个区间内的数字在将来的转移之中都是等价的。
这样优化之后状态数就会大大减少,足以AC。为了加速可以使用hash表代替map,也可以在转移时使用位运算进行优化。
关于复杂度分析,可以证明最坏情况下,总的状态数是将n拆成若干个整数的最大乘积的级别,也就是O(3^{n/3})。 

/*
51 nod 1775 LIS Counting 
首先必须要晓得利用单调栈求LIS的经典做法(不会的自己百度) 
而此题就是基于单调栈实现的 ,可以考虑从前往后讨论每一个字符加入还是不加人 , 这样便可以得到母串的所有子序列,而对于所有的子序列都要求得LIS显然是不可能的,
其实通过分析可以知道当前第i个字符,不管加不加入 ,之前的前i-1个字符的LIS是单调不减的 ,所以可以考虑利用之前的状态通过转移直接得到后面的状态。这很显然是个
dp ,对于代码中的两个函数Choose , Not_Choose , 分别为当前字符加入和不加人 , 为什么要Not_Choose ,因为用的是滚动数组 , 所以必须将前面的所有状态进行转移
由于35对于状态压缩来说还是比较大了,而LIS并不强调值本身, 值需要大小关系, 所以二进制位i+1~35并不代表i+1 ~ 35 是否在栈中, 而是表示比i大的有k个(k表示i+1 ~35 有1的位数)
而Not_choose 使得sta发生变化 , 但本质值与值之间大小关系并没有变化。
如果没有看懂的 可以结合官方题解在进行理解:
求LIS的经典做法是维护一个单调栈,然后从左到右扫,添加一个新元素时,找到栈中第一个比它大的元素,并用这个元素替换;如果找不到,则加到栈顶。最后栈的元素个数就是LIS长度。

现在我们要DP这个东西。一个自然的想法是把这个栈的状态压成一个n位01串s。dp状态就是f[i][s]表示前i个数中取了若干,栈中的情况是s的方案数。

然而n是35,直接这么搞的话状态数比较大。需要优化。

观察一下转移的过程,做到第i个数时,将来会被插入的数字只有a[i+1],a[i+2],...,a[n],因此将来的转移只和栈中数字与这些数字的大小关系有关。把a[i+1..n]的数字排序后设为b[1]<b[2]<..<b[n-i],那么dp状态实际上只需要记录栈中的数字在区间(0,b[1]),(b[1],b[2]),(b[2],b[3]),...里分别有几个,而不需要具体记录是哪些数字,因为目前在同一个区间内的数字在将来的转移之中都是等价的。

这样优化之后状态数就会大大减少,足以AC。为了加速可以使用hash表代替map,也可以在转移时使用位运算进行优化。

关于复杂度分析,可以证明最坏情况下,总的状态数是将n拆成若干个整数的最大乘积的级别,也就是O(3^{n/3})。 
*/
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 300
#define Mod 5000007
ll a[N] , b[N] , f[2][Mod + 10] , st[2][Mod + 10] , tot[2] , vis[Mod + 10] , now , lst , n , Ans[N];
inline void push(ll sta , ll k)
{
	ll pos = sta % Mod;
	if(vis[pos])
	{
		if(st[now][vis[pos]] == sta)
		{
			f[now][vis[pos]] += k; return ;
		}
		++pos;
		if(pos == Mod) pos = 0;
	}
	vis[pos] = ++tot[now];
	st[now][tot[now]] = sta;
	f[now][tot[now]] = k;
}

inline void Not_Choose(ll x , ll sta , ll k)
{
	ll i , num , j , pre_sta = sta , r; sta = 0;
	if(x == n)
	{
		for(i = 1 , num = 0 ; i <= n ; ++i)
		   if((pre_sta & (1ll << i))) ++num , sta |= (1ll << num);
		push(sta , k);
		return ;
	}
	for(i = 1 , num = 0 ; i < b[x + 1] ; ++i)
	   if((pre_sta & (1ll << i))) ++num , sta |= (1ll << num);
	
	for(i = x + 1; i <= n ; ++i)
	    for(j = b[i] , num = b[i] , r = (i == n ? n : b[i + 1]) ; j <= r; ++j)
	        if((pre_sta & (1ll << j))) ++num , sta |= (1ll << num);
	push(sta , k);
}

inline void Choose(ll x , ll sta , ll k)
{
	ll i;
	if(sta < (1ll << a[x])) sta |= (1ll << a[x]);
	else
	{
		sta |= (1ll << a[x]);
		for(i = a[x] + 1 ; ; ++i)
		{
			if(((sta >> i) & 1))
			{
				sta ^= (1ll << i); break;
			}
		}
	}
	Not_Choose(x , sta , k);
}

int main()
{
//	freopen("test.in" , "r" , stdin);
	ll i , j , k , cnt , sta;
	scanf("%lld" , &n);
	for(i = 1; i <= n; ++i) scanf("%lld" , &a[i]);
	lst = 1 , now = 0;
	push(0 , 1);
	for(i = 1 ; i <= n ; ++i)
	{
		lst ^= 1 , now ^= 1;
		memset(vis , 0 , sizeof(vis)) , tot[now] = 0;
		for(j = i + 1 ; j <= n ; ++j) b[j] = a[j];
		if(i != n) sort(b + i + 1 , b + n + 1);
		for(j = 1 ; j <= tot[lst] ; ++j)
		{
			cnt = f[lst][j] , sta = st[lst][j];
			Choose(i , sta , cnt);
			Not_Choose(i , sta , cnt);
		}
	}
	for(i = 1 ; i <= tot[now] ; ++i)
	{
		cnt = 0;
		for(j = 1 ; j <= n ; ++j) if(((st[now][i] >> j) & 1)) ++cnt;
		Ans[cnt] += f[now][i];
	}
	for(i = 0 ; i <= n ; ++i) printf("%lld\n" , Ans[i]);
	return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值