bzoj3990 [SDOI2015]排序 dfs

66 篇文章 0 订阅

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

100%的数据, 1<=N<=12.

Solution


注意到我们操作的顺序无关,可以钦定从小到大操作然后乘上阶乘求答案
然后我就不会做了,好劲啊

考虑dfs。假设我们当前dfs到第i层,操作块的大小为2^i。首先找到不连续的、长度为2^i的块,分类

  1. 如果不存在就dfs下一层
  2. 如果只有一个块那么交换这个块内的前后两半
  3. 如果有两个块那么枚举两个块前后共四半的四种交换方式
  4. 如果不止两个块那么就退掉

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int N=20005;

LL fac[N],ans;
int a[N],n;

int read() {
    int x=0,v=1; char ch=getchar();
    for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
    for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
    return x*v;
}

bool check(int st,int len) {
    rep(i,st+1,st+len-1) {
        if (a[i]!=a[i-1]+1) return true;
    }
    return false;
}

void swap(int st1,int st2,int len) {
    rep(i,1,len) {
        std:: swap(a[st1+i-1],a[st2+i-1]);
    }
}

void dfs(int dep,int stp) {
    if (dep>n) {
        return (void) (ans+=fac[stp]);
    }
    int pos1=0,pos2=0;
    for (int i=1;i<=(1<<n);i+=(1<<dep)) {
        if (!check(i,(1<<dep))) continue;
        if (!pos1) pos1=i;
        else if (!pos2) pos2=i;
        else return ;
    }
    if (!(pos1+pos2)) dfs(dep+1,stp);
    else if (!pos2) {
        swap(pos1,pos1+(1<<dep-1),1<<dep-1);
        if (!check(pos1,1<<dep)) dfs(dep+1,stp+1);
        swap(pos1,pos1+(1<<dep-1),1<<dep-1);
    } else {
        rep(i,0,1) rep(j,0,1) {
            swap(pos1+i*(1<<dep-1),pos2+j*(1<<dep-1),1<<dep-1);
            if (!check(pos1,1<<dep)&&!check(pos2,1<<dep)) {
                dfs(dep+1,stp+1);
            }
            swap(pos1+i*(1<<dep-1),pos2+j*(1<<dep-1),1<<dep-1);
        }
    }
}

int main(void) {
    n=read();
    fac[0]=1; rep(i,1,(1<<n)) fac[i]=1LL*i*fac[i-1];
    rep(i,1,(1<<n)) a[i]=read();
    dfs(1,0); printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值