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;
}