问题描述
小A有一个1-2N的排列A[1..2N],他希望将A数组从小到大排序,小A可以执行的操作有N种,每种操作最多可以执行一次,对于所有的
i(1<=i<=N),第i中操作为将序列从左到右划分为2{N-i+1}段,每段恰好包括2{i-1}个数,然后整体交换其中两段.小A想知道可以将数组A从小到
大排序的不同的操作序列有多少个,小A认为两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同).
下面是一个操作事例:
N=3,A[1..8]=[3,6,1,2,7,8,5,4].
第一次操作,执行第3种操作,交换A[1..4]和A[5..8],交换后的A[1..8]为[7,8,5,4,3,6,1,2].
第二次操作,执行第1种操作,交换A[3]和A[5],交换后的A[1..8]为[7,8,3,4,5,6,1,2].
第三次操作,执行第2中操作,交换A[1..2]和A[7..8],交换后的A[1..8]为[1,2,3,4,5,6,7,8].
输入格式
第一行,一个整数N第二行,2N个整数,A[1..2N]。
输出格式
一个整数表示答案
样例输入
3
7 8 5 6 1 2 4 3
样例输出
6
数据范围
100%的数据, 1<=N<=12.
解析
首先可以想到的是每次排序交换的两个块中一定是有序的,那么我们从小到大讨论每一种操作,如果对于第(i)种操作,序列每个长度为(2^{i-1})的块都是有序的,那么就看长度为(2^i)的块有哪些不是单调递增的。如果不满足要求的块的数量大于2,就无解。否则若数量为1,就交换那一块的两半;数量为2就分4部分两两交换。至于有序的判断,可以用排列的性质,如果块最右边的值减去最左边的值等于块长,说明有序。
另外,对于一个操作序列,任意交换两个元素是没有影响的,所以每找到一个长为(n)的操作序列,都要增加(n!)中方案。
代码
#include <iostream>
#include <cstdio>
#define N 5000
using namespace std;
int n=1,m,i,a[N];
long long ans,f[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
void dfs(int x,int sum)
{
int gap=(1<<(x-1)),gap1=gap<<1;
for(int i=1;i<=n&&x>1;i+=gap){
if(a[i+gap-1]-a[i]!=gap-1) return;
}
if(x==m+1){
ans+=f[sum];
return;
}
dfs(x+1,sum);
int op[5],cnt=0;
for(int i=1;i<=n;i+=gap1){
int j=(2*i+gap1-1)/2+1;
if(a[i+gap1-1]-a[i]!=gap1-1){
op[++cnt]=i;
op[++cnt]=j;
}
}
if(cnt>4) return;
for(int i=1;i<=cnt;i++){
for(int j=i+1;j<=cnt;j++){
for(int k=0;k<gap;k++) swap(a[op[i]+k],a[op[j]+k]);
dfs(x+1,sum+1);
for(int k=0;k<gap;k++) swap(a[op[i]+k],a[op[j]+k]);
}
}
}
int main()
{
m=read();
for(i=1;i<=m;i++) n*=2;
f[0]=1;
for(i=1;i<=12;i++) f[i]=f[i-1]*i;
for(i=1;i<=n;i++) a[i]=read();
dfs(1,0);
printf("%lld
",ans);
return 0;
}