【bzoj3990】【SDOI2015】【排序】【dfs】

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.

题解:

         首先如果一个操作序列合法,那它的全排列也一定都合法。

         所以我们只要搜索每个操作选还是不选。

         对于每个操作,如果执行它之前存在两个以上不连续递增的块,那肯定无解。

         如果不存在,那这个操作就没有必要执行。

         如果存在一个,就交换一下,继续dfs.

         如果存在两个,分四种情况交换,找到其中合法的状态dfs.

         这样搜到最后一定是合法的。直接将操作数的阶乘累加进答案即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int ans,t[20],fac[20],a[50010],n;
bool judge(int x,int k){
  for (int i=1;i<t[k];i++) if (a[x+i]!=a[x+i-1]+1) return false;
  return true;
}
void swap(int x,int y,int k){
  for (int i=0;i<t[k];i++)
   swap(a[x+i],a[y+i]);
}
void dfs(int x,int k){
  if(x==n+1){
    ans+=fac[k];
	return; 
  } 
  int t1(0),t2(0);
  for (int i=1;i<=t[n];i+=t[x])
   if (!judge(i,x)){
     if (!t1) t1=i;
     else if (!t2) t2=i;
	 else return; 
   }
  if (!t1&&!t2) dfs(x+1,k);
  else if (t1&&!t2){
     swap(t1,t1+t[x-1],x-1);
     dfs(x+1,k+1);
     swap(t1,t1+t[x-1],x-1);
  }
  else
   for (int i=0;i<=1;i++)
    for (int j=0;j<=1;j++){
      swap(t1+i*t[x-1],t2+j*t[x-1],x-1);
	  if (judge(t1,x)&&judge(t2,x)){
	    dfs(x+1,k+1);
	    swap(t1+i*t[x-1],t2+j*t[x-1],x-1);
        break;
	  }
	  swap(t1+i*t[x-1],t2+j*t[x-1],x-1);
    }
}
int main(){
  scanf("%d",&n);t[0]=1;fac[1]=1;
  for (int i=1;i<=n;i++) t[i]=t[i-1]*2;
  for (int i=2;i<=n;i++) fac[i]=fac[i-1]*i; 
  for (int i=1;i<=t[n];i++) scanf("%d",&a[i]);
  dfs(1,0);	
  cout<<ans<<endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值