考试遇见这种题目只能傻眼。。
题目大意:给定一个长度为2^n的排列,有n个操作,第i个操作为【将序列分成2^(n-i+1)段,每段长2^(i-1),然后任选两段交换】,每个操作最多用一次,求有多少操作序列能把序列排出来
(orz popoqqq)
orz hzwer:
我们可以发现一个操作序列如果改换顺序,结果不变,所以我们只要找到一种以后把答案加上这个操作序列的阶乘就好了。
我们从小到大DFS,对于第i次操作我们将序列分成2^(n-i)段,每段长度2^i。
然后如果发现有两个以上的段不连续那么这很明显没用。
如果没有连续的段,那么不操作。
如果有一个这样的段,判断将这个段的前半部分和后半部分交换后是否连续递增,如果是就交换然后继续DFS
如果有两个这样的段,判断四种交换情况然后DFS
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
typedef long long ll;
int n,bin[15],fac[15],a[N];
ll ans;
bool check(int x,int y)
{
fo(i,1,y-1)
if (a[x+i-1]+1!=a[x+i])return 1;
return 0;
}
inline void swap(int x,int y,int len)
{
fo(i,0,len-1)
swap(a[x+i],a[y+i]);
}
inline void dfs(int x,int y)
{
if (x>n)
{
ans+=fac[y];
return ;
}
int pos1=0,pos2=0;
for(int i=1;i<=bin[n];i+=bin[x])
if(check(i,bin[x]))
{
if (!pos1)pos1=i;
else if (!pos2)pos2=i;
else return;
}
if (!pos1&&!pos2)dfs(x+1,y);
else if (!pos2)
{
swap(pos1,pos1+bin[x-1],bin[x-1]);
if (!check(pos1,bin[x]))dfs(x+1,y+1);
swap(pos1,pos1+bin[x-1],bin[x-1]);
}
else
{
fo(i,0,1)
fo(j,0,1)
{
swap(pos1+i*bin[x-1],pos2+j*bin[x-1],bin[x-1]);
if (!check(pos1,bin[x])&&!check(pos2,bin[x]))
{
dfs(x+1,y+1);
swap(pos1+i*bin[x-1],pos2+j*bin[x-1],bin[x-1]);
break;
}
swap(pos1+i*bin[x-1],pos2+j*bin[x-1],bin[x-1]);
}
}
}
int main()
{
scanf("%d",&n);
bin[0]=fac[0]=1;
fo(i,1,n)bin[i]=bin[i-1]*2,fac[i]=fac[i-1]*i;
fo(i,1,bin[n])scanf("%d",&a[i]);
dfs(1,0);
printf("%lld\n",ans);
}