题目大意:给定一个长度为2^n的排列,有n个操作,第i个操作为【将序列分成2^(n-i+1)段,每段长2^(i-1),然后任选两段交换】,每个操作最多用一次,求有多少操作序列能把序列排出来
Orz dzy
首先我们很容易发现一个操作序列是否合法与序列的顺序是无关的
因此我们只需要确定某个操作序列中每个操作选不选就行了 那么这类操作序列对答案的贡献就是选择的操作数的阶乘
我们从小到大DFS,对于第i次操作我们将序列分成2^(n-i)段,每段长度2^i
我们找到序列中不是连续递增的段,如果这样的段超过2个,显然就废了
如果没有这样的段,就不需要执行这个操作
如果有一个这样的段,判断将这个段的前半部分和后半部分交换后是否连续递增,如果是就交换然后继续DFS
如果有两个这样的段,判断四种交换情况然后DFS
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M (1<<12) using namespace std; int n; int a[M]; long long fac[15],ans; void Swap(int a[],int b[],int len) { int i; for(i=0;i<len;i++) swap(a[i],b[i]); } void DFS(int dpt,int cnt) { if(dpt==n) { ans+=fac[cnt]; return ; } int stack[3]={0,0,0},top=0; int i,j,temp=1<<dpt+1; for(i=0;i<1<<n;i+=temp) { if( a[i+(temp>>1)-1]+1!=a[i+(temp>>1)] ) { if(top==2) return ; stack[++top]=i; } } if(top==0) { DFS(dpt+1,cnt); return ; } if(top==1) { i=stack[1]; if( a[i+temp-1]+1==a[i] ) { Swap(a+i,a+i+(temp>>1),temp>>1); DFS(dpt+1,cnt+1); Swap(a+i,a+i+(temp>>1),temp>>1); } return ; } if(top==2) { i=stack[1];j=stack[2]; if( a[i+(temp>>1)-1]+1==a[j+(temp>>1)] && a[j+(temp>>1)-1]+1==a[i+(temp>>1)] ) { Swap(a+i,a+j,temp>>1); DFS(dpt+1,cnt+1); Swap(a+i,a+j,temp>>1); Swap(a+i+(temp>>1),a+j+(temp>>1),temp>>1); DFS(dpt+1,cnt+1); Swap(a+i+(temp>>1),a+j+(temp>>1),temp>>1); } if( a[j+(temp>>1)-1]+1==a[i] && a[j+temp-1]+1==a[i+(temp>>1)] ) { Swap(a+i,a+j+(temp>>1),temp>>1); DFS(dpt+1,cnt+1); Swap(a+i,a+j+(temp>>1),temp>>1); } if( a[i+(temp>>1)-1]+1==a[j] && a[i+temp-1]+1==a[j+(temp>>1)] ) { Swap(a+j,a+i+(temp>>1),temp>>1); DFS(dpt+1,cnt+1); Swap(a+j,a+i+(temp>>1),temp>>1); } return ; } } int main() { int i; cin>>n; for(i=0;i<1<<n;i++) scanf("%d",&a[i]); for(fac[0]=1,i=1;i<=n;i++) fac[i]=fac[i-1]*i; DFS(0,0); cout<<ans<<endl; return 0; }
原文地址:http://blog.csdn.net/popoqqq/article/details/45073989