把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

SDOI2015 排序【搜索-思考-细节】

题目链接

题意简述

给定一个长度为\(2^n\)的排列,有\(n\)种操作,第\(i\)种操作为:将序列分成\(2^{n-i+1}\)段,每段恰好包含\(2^{i-1}\)个数,然后任选其中两段进行交换。每个操作最多用一次,求有多少操作序列能把序列按照从小到大排序。

题目解析

数据范围这么小,可以先考虑考虑爆搜啊。

但是我没有想出来,看了题解很疑惑为什么都在说操作顺序不影响,结果是我自己把题意理解错了,它所谓的“操作位置不同”是指一个操作在操作序列中的位置不同,即操作顺序不一样,而不是操作时选择的那两段位置不一样。

好的,操作序列的顺序对答案最后长什么样没有任何影响,因为你可以选任意两段进行交换,无论顺序怎样,你都只需要在操作的时候怼着你最后的那个结果操作就可以了,而这\(n\)个操作又没有交集,相互独立,没有影响。

那么只需要确定选哪些操作就可以了,答案要再乘上一个\(len!\)\(len\)是操作序列长度

接下来用搜索确定选哪些操作。从小到大枚举每种操作,对于第\(i\)种操作,我们把序列分成\(2^{n-i}\)段,每段\(2^{i}\)个数,找到序列中不是连续递增的段。如果这样的段超过两个,那显然救不回来了,因为一种操作只能操作一次,而下一次操作的最小单位就是现在分好的段的大小。

如果这样的段有\(1\)个,就交换这个段的前后两半,判断是否合法,如果合法可以继续搜下去,不合法也救不回来了,原因同上。

如果这样的段有\(2\)个,就有四种情况可以交换(这里不会再出现自己的前半段和自己的后半段交换的情况了,因为如果自己内部解决的话,别人也无法解决,而且由于是个排列,所以实际上这样的话自己也是无法解决的呢),分别讨论一下。

当然如果这样的段没有的话,就不用交换,直接往下搜。

复杂度分析的话,枚举每个操作要不要用似乎是\(2^{12}\)的,但是复杂度大头显然在判断上面,每次判断还要跑一次\(2^{12}\),姑且认为它是\(2^{12}\times 2^{12}=2^{24}\)吧,但反正是\(O(跑得过)\)


►Code View

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define LL long long
#define N 5000
#define INF 0x3f3f3f3f
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
	return f*x;
}
int n;
int pw[20],fac[20],a[N];
LL ans;
void Init()
{
	pw[0]=1,fac[0]=1;
	for(int i=1;i<=12;i++)
		pw[i]=pw[i-1]<<1,fac[i]=fac[i-1]*i;
}
bool check(int x,int k)//起点 长度 [x,x+pw[k]-1] 
{
	for(int i=1;i<pw[k];i++)
		if(a[x+i]!=a[x+i-1]+1) return 0;
	return 1;
}
void Swap(int x,int y,int k)
{
	int tmp;
	for(int i=0;i<pw[k];i++)
		tmp=a[x+i],a[x+i]=a[y+i],a[y+i]=tmp;
}
void dfs(int k,int len)//段的长度 2^k 已有的操作序列长度:len
{
	if(k==n+1)
	{
		ans+=fac[len];
		return ;
	}
	int p1=0,p2=0;//两个不连续递增段的位置
	for(int i=1;i<=pw[n];i+=pw[k])//段的起点
		if(!check(i,k))
		{
			if(!p1) p1=i;
			else if(!p2) p2=i;
			else return ;//超过2个这样的段 无解了 
		}
	if(!p1&&!p2) dfs(k+1,len);//没有这样的段
	else if(p1&&!p2)
	{//只有一个这样的段 
		Swap(p1,p1+pw[k-1],k-1);
		if(check(p1,k)) dfs(k+1,len+1);
		Swap(p1,p1+pw[k-1],k-1);
	}
	else
	{
		for(int i=0;i<=1;i++)
			for(int j=0;j<=1;j++)
			{
				Swap(p1+i*pw[k-1],p2+j*pw[k-1],k-1);
				if(check(p1,k)&&check(p2,k))
					dfs(k+1,len+1);
				Swap(p1+i*pw[k-1],p2+j*pw[k-1],k-1);
			}
	}
} 
int main()
{
	Init();
	n=rd();
	for(int i=1;i<=pw[n];i++)
		a[i]=rd();
	dfs(1,0);
	printf("%lld\n",ans); 
	return 0;
}
posted @ 2020-11-19 22:28  Starlight_Glimmer  阅读(121)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end