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].
100%的数据, 1<=N<=12.
Solution
注意到我们操作的顺序无关,可以钦定从小到大操作然后乘上阶乘求答案
然后我就不会做了,好劲啊
考虑dfs。假设我们当前dfs到第i层,操作块的大小为2^i。首先找到不连续的、长度为2^i的块,分类
- 如果不存在就dfs下一层
- 如果只有一个块那么交换这个块内的前后两半
- 如果有两个块那么枚举两个块前后共四半的四种交换方式
- 如果不止两个块那么就退掉
Code
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
typedef long long LL;
const int N=20005;
LL fac[N],ans;
int a[N],n;
int read() {
int x=0,v=1; char ch=getchar();
for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
return x*v;
}
bool check(int st,int len) {
rep(i,st+1,st+len-1) {
if (a[i]!=a[i-1]+1) return true;
}
return false;
}
void swap(int st1,int st2,int len) {
rep(i,1,len) {
std:: swap(a[st1+i-1],a[st2+i-1]);
}
}
void dfs(int dep,int stp) {
if (dep>n) {
return (void) (ans+=fac[stp]);
}
int pos1=0,pos2=0;
for (int i=1;i<=(1<<n);i+=(1<<dep)) {
if (!check(i,(1<<dep))) continue;
if (!pos1) pos1=i;
else if (!pos2) pos2=i;
else return ;
}
if (!(pos1+pos2)) dfs(dep+1,stp);
else if (!pos2) {
swap(pos1,pos1+(1<<dep-1),1<<dep-1);
if (!check(pos1,1<<dep)) dfs(dep+1,stp+1);
swap(pos1,pos1+(1<<dep-1),1<<dep-1);
} else {
rep(i,0,1) rep(j,0,1) {
swap(pos1+i*(1<<dep-1),pos2+j*(1<<dep-1),1<<dep-1);
if (!check(pos1,1<<dep)&&!check(pos2,1<<dep)) {
dfs(dep+1,stp+1);
}
swap(pos1+i*(1<<dep-1),pos2+j*(1<<dep-1),1<<dep-1);
}
}
}
int main(void) {
n=read();
fac[0]=1; rep(i,1,(1<<n)) fac[i]=1LL*i*fac[i-1];
rep(i,1,(1<<n)) a[i]=read();
dfs(1,0); printf("%lld\n", ans);
return 0;
}