Description
小A有一个1-2^N的排列A[1..2^N],他希望将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].
Input
第一行,一个整数N
第二行,2^N个整数,A[1..2^N]
Output
一个整数表示答案
Sample Input
3
7 8 5 6 1 2 4 3
Sample Output
6
HINT
100%的数据, 1<=N<=12.
Source
Round 1 感谢ZKY制作非官方数据
有一个结论就是操作顺序不影响结果
所以只要DFS出合法操作集合,对答案的贡献是集合元素个数的阶乘
考虑DFS的每步能让一个2^i的段变成连续递增的序列然后从小到大搜
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define MAXN 15
#define GET (ch>='0'&&ch<='9')
using namespace std;
int n;
int a[(1<<MAXN)];
int Pow[MAXN]={1},fac[MAXN]={1};
LL ans;
void in(int &x)
{
char ch=getchar();x=0;
while (!GET) ch=getchar();
while (GET) x=x*10+ch-'0',ch=getchar();
}
bool check(int x,int len)
{
for (int i=1;i<Pow[len];i++) if (a[i+x-1]+1!=a[i+x]) return 0;
return 1;
}
void swap(int x,int y,int len)
{
for (int i=0;i<Pow[len];i++) swap(a[i+x],a[i+y]);
}
void dfs(int len,int cnt)
{
if (len==n+1) { ans+=fac[cnt];return; }
int s1=0,s2=0;
for (int i=1;i<=Pow[n];i+=Pow[len])
if (!check(i,len))
{
if (!s1) s1=i;
else if(!s2) s2=i;
else return;
}
if (!s1&&!s2) dfs(len+1,cnt);
else
if (s1&&!s2) swap(s1,s1+Pow[len-1],len-1),dfs(len+1,cnt+1),swap(s1,s1+Pow[len-1],len-1);
else
{
for (int i=0;i<=1;i++)
for (int j=0;j<=1;j++)
{
swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);
if (check(s1,len)&&check(s2,len)) { dfs(len+1,cnt+1);swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);break; }
swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);
}
}
}
int main()
{
in(n);
for (int i=1;i<=n;i++) Pow[i]=Pow[i-1]<<1,fac[i]=fac[i-1]*i;
for (int i=1;i<=Pow[n];i++) in(a[i]);
dfs(1,0);cout<<ans<<endl;
}