【SDOI2015】【BZOJ3990】排序

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.

Source

Round 1 感谢ZKY制作非官方数据

有一个结论就是操作顺序不影响结果
所以只要DFS出合法操作集合,对答案的贡献是集合元素个数的阶乘
考虑DFS的每步能让一个2^i的段变成连续递增的序列然后从小到大搜

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define MAXN 15
#define GET (ch>='0'&&ch<='9')
using namespace std;
int n;
int a[(1<<MAXN)];
int Pow[MAXN]={1},fac[MAXN]={1};
LL ans;
void in(int &x)
{
    char ch=getchar();x=0;
    while (!GET)    ch=getchar();
    while (GET) x=x*10+ch-'0',ch=getchar();
}
bool check(int x,int len)
{
    for (int i=1;i<Pow[len];i++)    if (a[i+x-1]+1!=a[i+x]) return 0;
    return 1;
}
void swap(int x,int y,int len)
{
    for (int i=0;i<Pow[len];i++)    swap(a[i+x],a[i+y]);
}
void dfs(int len,int cnt)
{
    if (len==n+1)   {   ans+=fac[cnt];return;   }
    int s1=0,s2=0;
    for (int i=1;i<=Pow[n];i+=Pow[len])
        if (!check(i,len))
        {
            if (!s1)    s1=i;
            else    if(!s2) s2=i;
            else    return;
        }
    if (!s1&&!s2)   dfs(len+1,cnt);
    else
    if (s1&&!s2)    swap(s1,s1+Pow[len-1],len-1),dfs(len+1,cnt+1),swap(s1,s1+Pow[len-1],len-1);
    else
    {
        for (int i=0;i<=1;i++)
            for (int j=0;j<=1;j++)
            {
                swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);
                if (check(s1,len)&&check(s2,len))   {   dfs(len+1,cnt+1);swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);break; }
                swap(s1+i*Pow[len-1],s2+j*Pow[len-1],len-1);
            }
    }
}
int main()
{
    in(n);
    for (int i=1;i<=n;i++)  Pow[i]=Pow[i-1]<<1,fac[i]=fac[i-1]*i;
    for (int i=1;i<=Pow[n];i++) in(a[i]);
    dfs(1,0);cout<<ans<<endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值