题目
题目链接:https://www.luogu.com.cn/problem/P3322
小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]\).
思路
首先有一个明显的结论:对于一组合法的交换方案,任意调换操作顺序的最终结果是一样的。
所以我们可以从小到大搜索,最终方案数乘上操作次数的阶乘。
假设我们现在搜索到交换两个长度为\(2^k\)的序列,那么我们就将这一个序列划分成若干个长度为\(2^{k+1}\)的序列,显然我们需要满足在交换完两个区间后,每一个长度为\(2^{k+1}\)的序列要为一个公差为\(1\)的递增序列,这样我们才可能可以把它排好序。
那么假设对于所有的长度为\(2^{k+1}\)的序列,仅有1个不满足递增且连续,那么我们只需将这个序列前后两端长度为\(2^k\)调换,如果此时满足递增且连续,就继续往下搜索,否则该方案一定不成立。
如果有2个序列不满足递增且连续,我们就将这2个长度为\(2^{k+1}\)的序列分成的4个长度为\(2^k\)的序列,一共有4种交换方法,全部尝试一遍即可。
时间复杂度\(O(n\times 2^{2n})\)。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5000;
int n,a[N];
ll ans;
ll fac(int x)
{
ll s=1;
for (ll i=2;i<=x;i++) s*=i;
return s;
}
bool check(int l,int r)
{
for (int i=l+1;i<=r;i++)
if (a[i-1]!=a[i]-1) return 0;
return 1;
}
void Swap(int i,int j,int k)
{
for (int l=1;l<=k;l++,i++,j++)
swap(a[i],a[j]);
}
void dfs(int x,int k)
{
if (x>n)
{
if (check(1,(1<<n))) ans+=fac(k);
return;
}
int cnt=0,pos[5];
for (int i=1;i<=(1<<n);i+=(1<<x))
if (!check(i,i+(1<<x)-1))
{
pos[++cnt]=i;
if (cnt>2) return;
}
if (cnt==1)
{
Swap(pos[1],pos[1]+(1<<x-1),(1<<x-1));
if (check(pos[1],pos[1]+(1<<x)-1)) dfs(x+1,k+1);
Swap(pos[1],pos[1]+(1<<x-1),(1<<x-1));
}
else if (cnt==2)
{
Swap(pos[1],pos[2],(1<<x-1));
if (check(pos[1],pos[1]+(1<<x)-1) && check(pos[2],pos[2]+(1<<x)-1)) dfs(x+1,k+1);
Swap(pos[1],pos[2],(1<<x-1));
Swap(pos[1],pos[2]+(1<<x-1),(1<<x-1));
if (check(pos[1],pos[1]+(1<<x)-1) && check(pos[2],pos[2]+(1<<x)-1)) dfs(x+1,k+1);
Swap(pos[1],pos[2]+(1<<x-1),(1<<x-1));
Swap(pos[1]+(1<<x-1),pos[2],(1<<x-1));
if (check(pos[1],pos[1]+(1<<x)-1) && check(pos[2],pos[2]+(1<<x)-1)) dfs(x+1,k+1);
Swap(pos[1]+(1<<x-1),pos[2],(1<<x-1));
Swap(pos[1]+(1<<x-1),pos[2]+(1<<x-1),(1<<x-1));
if (check(pos[1],pos[1]+(1<<x)-1) && check(pos[2],pos[2]+(1<<x)-1)) dfs(x+1,k+1);
Swap(pos[1]+(1<<x-1),pos[2]+(1<<x-1),(1<<x-1));
}
else dfs(x+1,k);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=(1<<n);i++)
scanf("%d",&a[i]);
dfs(1,0);
printf("%lld",ans);
return 0;
}