BZOJ 3990: [SDOI2015]排序

本文介绍了一种通过特定操作序列来实现数组排序的方法,并提供了一个详细的示例和完整的C++代码实现。该方法关注如何计算可能的不同操作序列的数量,以完成从小到大的排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:小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].

观察题目中的操作,我们发现每种操作只能进行一次,也就是只能交换两个块,并且i+1的操作并不能改变i的操作的块中大小关系,所以操作的顺序并不影响,并且每次操作的情况很有限,比如在第i次操作中,我们只需判断大小为2^i的块内是否有序,如果只有一个块无序,只能将这个块首位互换,如果有两个块可以两两交叉,如果有三个块及以上则肯定无解,爆搜就可以了

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=5000+10;
int a[maxn],n,bow[20],hm[20];
long long ans;
bool check(int l,int r)
{
  for(int i=l+1;i<=r;i++)
    if(a[i]!=a[i-1]+1) return false;
  return true;
}
void swap(int l,int r,int l1,int r1)
{
  for(int i=l,j=l1;i<=r;i++,j++)
    swap(a[i],a[j]);
}
void dfs(int p,int use)
{  
  int v1=0,v2=0;
  if(p==n+1)
  {
    ans+=hm[use];
    return;
  }
  for(int i=1;i<=bow[n];i+=bow[p])
    if(!check(i,i+bow[p]-1))
    {
      if(!v1) v1=i;
      else if(!v2) v2=i;
      else return;
    }
  if(!v1&&!v2) dfs(p+1,use);
  if(v1&&!v2)
  {
    swap(v1,v1+bow[p-1]-1,v1+bow[p-1],v1+bow[p]-1);
    dfs(p+1,use+1);
    swap(v1,v1+bow[p-1]-1,v1+bow[p-1],v1+bow[p]-1);
  }
  if(v1&&v2)
  {
    for(int x=0;x<2;x++)
      for(int y=0;y<2;y++)
      {
        swap(v1+x*bow[p-1],v1+bow[p-1]*(x+1)-1,v2+y*bow[p-1],v2+bow[p-1]*(y+1)-1);
        if(check(v1,v1+bow[p]-1)&&check(v2,v2+bow[p]-1)) dfs(p+1,use+1);
        swap(v1+x*bow[p-1],v1+bow[p-1]*(x+1)-1,v2+y*bow[p-1],v2+bow[p-1]*(y+1)-1);
      }
  }
}  
int main()
{
  //freopen("3990.in","r",stdin);
  //freopen("3990.out","w",stdout);
  scanf("%d",&n);
  bow[0]=1;for(int i=1;i<=20;i++) bow[i]=bow[i-1]*2;
  hm[0]=1;for(int i=1;i<=12;i++) hm[i]=hm[i-1]*i; 
  for(int i=1;i<=bow[n];i++) scanf("%d",&a[i]);
  dfs(1,0);
  printf("%lld\n",ans);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值