3712. 【NOI2014模拟6.30】石中剑的考验(sword)

Description

小X 最近看了亚瑟王的传说,幻想自己也能成为这样的传奇人物。   

在梦中,小X 看到前方由近及远有n 个高度互不相同且均为整数的台阶,其中最矮的高度为1,最高的高度为n。

小X 隐约听见有人在说话:“拔出此石中剑者,即为英格兰之王。”顺着声音的方向,小X 看见了一把插在石缝中的装饰华美的剑。

 “莫非这就是……”小X 跑过去,恨不得立刻将其拔出。

然而,小X 接近它时就会被弹开。刚刚的声音再次响起:“看到那些台阶了吗?要想拔出石中剑,首先要跨过那些台阶。你可以选择从任意一个台阶出发,每次向前跨越若干个台阶,但必须保证每次落脚的台阶都高于上一次落脚的台阶。为了展现王的姿态,你要让落脚的次数尽量多。”

这可难不倒小X,他轻松地完成了任务,步伐在天空中划出了一道优美的弧线。

 “最后,你还要在心中默念一个数,才能得到石中剑的认可。记住自己刚才的轨迹了吗?你看那些台阶,其实都是虚幻的,可以任意改变顺序。石中剑需要你回答的是,那些台阶有多少种不同的排列方法,可以用你刚才的轨迹来完成之前的任务呢?”

思考了许久,小X 身上直冒汗。身为正义使者的你,想要帮助他在梦中成为英格兰之王。为此,你潜入了他的意识,得到了他刚才的轨迹。现在,你必须尽快得到答案,从而放入他的意识,使他通过石中剑的考验。

Input

第一行一个整数n。

第二行一个整数k,表示小X 刚才轨迹落脚的次数。

第三行k 个整数,表示这个轨迹依次落脚的台阶的高度。

Output

第一行一个整数,表示答案。

Sample Input

5

3

1 3 4

Sample Output

11

【样例解释】

    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)。

 

Data Constraint

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

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

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

Solution

状压dp。

其中原排列合法的充要条件是: 

1.给出的子序列是原排列的子序列;

2.原排列的最长上升子序列长度为k。

可以设f[S]表示选数的状态为S时这些数所构成的序列满足条件的方案数。

一个条件是比较容易满足的,可以用n位二进制数表示每个数是否出现过。考虑从当前状态出发进行转移,枚举序列的下一个数 x,有以下两种情况: 若 x 不在给出的子序列中,则可以直接加入;若 x 在给出的子序列中,则要求其在子序列中的前一个数已经在当前状态中出现过。

第二个条件是本题的瓶颈所在,我们的状态必须要具有能力在枚举序列的下一个数 x 后求出以x 结尾的最长上升子序列的长度。 显然,如果把以当前出现过的每个数结尾的最长上升子序列长度都记录下来是无法承受的。而要想不记录以当前出现过的每个数结尾的最长上升子序列长度就能转移, 这就使我们联想到经典的 O(nlogn) 求长度为 n 的序列的最 长上升子序列的算法。

在这个算法中,我们需要实时维护长度为 i 的上升子序列结尾的最小值,这是一个单调增的序列,在这里称其为最小结尾序列。 那么,我们同样可以用 n 位二进制数表示每个数是否在最小结尾序列中,这样利用其单调增的性质就能恢复出最小结尾序列,从而在枚举序列的下一个数 x 后以 O(n) 的时间复杂度求出以 x 结尾的最长上升子序列的长度。

  由于在最小结尾序列中的数一定是已经出现过的数,我们把上述两个 n 位二进制数合并为一个 n 位三进制数即可。

对于一个三进制数,0表示没有选,1表示选了但不在最小结尾序列中,2表示选了且在最小结尾序列中。

在转移的时候,我们还是从小到大枚举x,尝试将x加入,在满足上述条件1的前提下,还需要满足条件2。值得注意的是原本经典做法中二分的部分我们直接变成从前往后枚举,并且x变大后枚举的位置并不用还原,直接继续往后枚举即可。

还有就是注意到当我们转移时新加入一个数时它一定在最小结尾序列中,同时有可能会踢出一个在最小结尾序列的数,这样就使一个原本是2的数位变成了1,从而可能转移到一个较小的状态,那么我们可以将1和2所代表的含义互换一下,这样转移时我们就直接将枚举的数所在数位上变成1(表示选了并且在最小序列中),然后将踢出最小序列的数所在数位变成2(选了但不在最小序列)。这样就能成功转移了。

时间复杂度 O(3^n*n ),空间复杂度 O(3^n )。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define I int
#define F(i,a,b) for(I i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof(a))
#define N 20
using namespace std;
void rd(I &x){
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
}
I n,k,a[N],s[N],p[N],T[N],f[14348908],ans;
I main(){
	freopen("sword.in","r",stdin);
	freopen("sword.out","w",stdout);
	rd(n),rd(k);
	T[0]=f[0]=1;
	F(i,1,n) T[i]=T[i-1]*3;
	F(i,1,k){
		rd(a[i]);
		p[a[i]]=a[i-1];
	}
	F(S,0,T[n]-1) if(f[S]){
		I x=S,l=0,to=0;
		mem(a,0);mem(s,0);
		while(x){a[++l]=x-(x/3)*3;x/=3;}
		F(i,1,l) if(a[i]){
			to++;
			if(a[i]==1) s[++s[0]]=i;
		}
		ans+=(to==n)*f[S];
		I j=1;
		F(i,1,n) if(!a[i]&&(!p[i]||a[p[i]])){
			if(i>s[s[0]]){
				if(s[0]+1<=k) f[S+T[i-1]]+=f[S];
			}
			else{
				for(;j<=s[0];j++) if(i<s[j]&&(i>s[j-1]||j==1)) break;
				if(j<=s[0]) f[S+T[i-1]+T[s[j]-1]]+=f[S];
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/103674075

发布了199 篇原创文章 · 获赞 201 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zsjzliziyang/article/details/103674075