【BZOJ3990】【SDOI2015】排序

21 篇文章 0 订阅

Description

小A有一个 12N 的排列 A[12N] ,他希望将 A 数组从小到大排序,小A可以执行的操作有 N 种,每种操作最多可以执行一次,对于所有的 i(1iN) ,第 i 中操作为将序列从左到右划分为 2Ni+1 段,每段恰好包括 2i1 个数,然后整体交换其中两段。小A想知道可以将数组 A 从小到大排序的不同的操作序列有多少个,小A认为两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同)。

下面是一个操作事例:
N=3,A[18]=[3,6,1,2,7,8,5,4]
第一次操作,执行第 3 种操作,交换 A[14] A[58] ,交换后的 A[18] [7,8,5,4,3,6,1,2]
第二次操作,执行第 1 种操作,交换 A[3] A[5] ,交换后的 A[18] [7,8,3,4,5,6,1,2]
第三次操作,执行第 2 种操作,交换 A[12] A[78] ,交换后的 A[18] [1,2,3,4,5,6,7,8]

Input

第一行,一个整数 N
第二行, 2N 个整数, A[12N]

Output

一个整数表示答案。

Sample Input

3
7 8 5 6 1 2 4 3

Sample Output

6

HINT

100% 的数据, 1N12

题解

这道题的想法真的很妙。
首先你需要发现一个神奇的性质,就是对于每一种可能的方案,每一种操作的顺序并不影响操作结果,影响操作结果的仅为选择操作种类的集合。所以对于每一种可行的操作集合 S ,它对答案的贡献为 |S|! 。转化为求所有可行的集合,由于 N 非常小, 2N 只有不到 104 的级别,所以完全可以直接爆搜出所有的解。
搜索的时候按照操作种类从小到大的顺序,因为只有这样才能在搜索比较大的区间时,小的区间已经有序。
假设当前搜索的操作种类为 t ,我们只需要判断对于所有无序的序列 [k×2t(k+1)×2t1] 的个数。如果个数 >2 ,那么无解;如果个数 =0 , 那么不选择当前操作;如果个数 =1 ,那么当前操作调整这个无序的区间;如果个数 =2 ,那么当前操作调整这两个无序的区间。

My Code

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>

using namespace std;
typedef long long ll;

ll fac[15], ans;
int n, N;
int a[10005];

int check(int pos, int k){
    for(int i = 0; i < (1 << k); i ++){
        if(a[pos + i] != a[pos] + i) return 0;
    }
    return 1;
}

void swp(int posx, int posy, int k){
    for(int i = 0; i < (1 << k); i ++){
        swap(a[posx + i], a[posy + i]);
    }
}
void dfs(int t, int cur){
    if(t == n){
        ans += fac[cur]; return;
    }
    int m = 1 << t; int m2 = m + m;
    int t1 = -1, t2 = -1;
    for(int i = 0; i < N; i += m2){
        if(!check(i, t + 1)){
            if(t1 == -1) t1 = i;
            else if(t2 == -1) t2 = i;
            else return;
        }
    }
    if(t1 == -1 && t2 == -1) dfs(t + 1, cur);
    else if(t2 == -1){
        swp(t1, t1 + m, t);
        dfs(t + 1, cur + 1);
        swp(t1, t1 + m, t);
    }else{
        for(int i = 0; i <= 1; i ++){
            for(int j = 0; j <= 1; j ++){
                swp(t1 + i * m, t2 + j * m, t);
                if(check(t1, t + 1) && check(t2, t + 1)){
                    dfs(t + 1, cur + 1);
                    swp(t1 + i * m, t2 + j * m, t);
                    break;
                }
                swp(t1 + i * m, t2 + j * m, t);
            }
        }
    }
}

int main(){
    scanf("%d", &n);
    N = 1 << n;
    for(int i = 0; i < N; i ++){
        scanf("%d", &a[i]);
    }
    fac[0] = 1;
    for(int i = 1; i <= n; i ++){
        fac[i] = fac[i - 1] * ll(i);
    }
    dfs(0, 0);
    printf("%lld\n", ans);
    return 0;
} 
/*
3
3 6 1 2 7 8 5 4

6
*/ 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值