BZOJ 3990(深搜思路题目)

题目:

小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].

分析:
首先由这样的一个性质,一个合法序列的操作的任意排列都合法,因为对于任意两个动作先执行哪一个产生的结果都一样(大范围的交换,对小范围内的点得相对位置没有影响)
再多说一句,这个内部是很重要的,与长为8的数组, 划分成4 ,4 ,其他层面的改变都是对4,4 内部的改变与4 ,4 的先对位置无关,所以具有无序性。
那么只需确定从大到小或从小到大枚举操作即可。
对于每次的处理,可以看成这样例如
8
7 8 5 6 1 2 4 3  那么对于第一种交换需要解决的问题是吧所有的段变成12 34 56 78 这样四个段,他们的顺序无所谓,因为只有该操作可以做到这一点。这样先统计有多少小段
内的两个数不是 递增且相差1的关系。那么多与2个显然没有解,进行可行性枚举,就直接过了此题。
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <list>
#include <cstdlib>
#include <queue>
#include <stack>
#include <cmath>
#include <bitset>
#include <cassert>
#define ALL(a) a.begin(), a.end()
#define clr(a, x) memset(a, x, sizeof a)
#define X first
#define Y second
#define pb push_back
#define lowbit(x) (x&(-x))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define rep1(i,x,y) for(int i=x;i<=y;i++)
#define rep(i,n) for(int i=0;i<(int)n;i++)
using namespace std;
const double eps = 1e-10;
typedef long long LL;
typedef long long ll;
typedef pair<int, int> pii;
const int oo =0x3f3f3f3f;

const int N = (1<<12)+10;
int id[N] , n , aa[N] , now[N] , ans , ji[30];
void dfs(int d,vector<int> a , int all){
   if(d == n+1){
       ans += ji[all];
       return ;
   }
   int cnt = 0, po[3];
   for(int i=1 , j=1;i<a.size();i+=2,j++){
       id[i+1] = j;
       if(a[i-1]+1 != a[i]){
            if(cnt == 2) return ;
            po[++cnt] = i;
       }
   }
   vector<int> te;
   if(!cnt){
       for(int i=1;i<a.size();i+=2) te.push_back(id[a[i]]);
       dfs(d+1,te,all);
       return ;
   }
   if(cnt == 1){
       int j = po[1];
       swap(a[j],a[j-1]);
       for(int i=1;i<a.size();i+=2) te.push_back(id[a[i]]);
       dfs(d+1,te,all+1);
       return ;
   }
   for(int i=-1;i<=0;i++)
      for(int j=-1;j<=0;j++){
         int pi = po[1]+i , pj = po[2]+j;
         swap(a[pi],a[pj]);
         if(a[po[1]-1]+1 == a[po[1]] && a[po[2]-1]+1 == a[po[2]]){
               te.clear();
               for(int i=1;i<a.size();i+=2) te.push_back(id[a[i]]);
               dfs(d+1,te , all+1);
         }
         swap(a[pi],a[pj]);
   }
}
int main()
{
    ji[0]=1;
    for(int i=1;i<=12;i++) ji[i] = ji[i-1]*i;
    scanf("%d",&n);
    vector<int> an;
    rep1(i,1,(1<<n)) scanf("%d",&aa[i]) , now[i] = aa[i] , an.push_back(aa[i]);
    ans = 0;
    dfs(1 , an , 0);
    cout<<ans<<endl;
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值