zoukankan      html  css  js  c++  java
  • [洛谷P3322] SDOI2015 排序

    问题描述

    小A有一个1-2N的排列A[1..2N],他希望将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].

    输入格式

    第一行,一个整数N第二行,2N个整数,A[1..2N]。

    输出格式

    一个整数表示答案

    样例输入

    3
    7 8 5 6 1 2 4 3

    样例输出

    6

    数据范围

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

    解析

    首先可以想到的是每次排序交换的两个块中一定是有序的,那么我们从小到大讨论每一种操作,如果对于第(i)种操作,序列每个长度为(2^{i-1})的块都是有序的,那么就看长度为(2^i)的块有哪些不是单调递增的。如果不满足要求的块的数量大于2,就无解。否则若数量为1,就交换那一块的两半;数量为2就分4部分两两交换。至于有序的判断,可以用排列的性质,如果块最右边的值减去最左边的值等于块长,说明有序。

    另外,对于一个操作序列,任意交换两个元素是没有影响的,所以每找到一个长为(n)的操作序列,都要增加(n!)中方案。

    代码

    #include <iostream>
    #include <cstdio>
    #define N 5000
    using namespace std;
    int n=1,m,i,a[N];
    long long ans,f[N];
    int read()
    {
    	char c=getchar();
    	int w=0;
    	while(c<'0'||c>'9') c=getchar();
    	while(c<='9'&&c>='0'){
    		w=w*10+c-'0';
    		c=getchar();
    	}
    	return w;
    }
    void dfs(int x,int sum)
    {
    	int gap=(1<<(x-1)),gap1=gap<<1;
    	for(int i=1;i<=n&&x>1;i+=gap){
    		if(a[i+gap-1]-a[i]!=gap-1) return;
    	}
    	if(x==m+1){
    		ans+=f[sum];
    		return;
    	}
    	dfs(x+1,sum);
    	int op[5],cnt=0;
    	for(int i=1;i<=n;i+=gap1){
    		int j=(2*i+gap1-1)/2+1;
    		if(a[i+gap1-1]-a[i]!=gap1-1){
    			op[++cnt]=i;
    			op[++cnt]=j;
    		}
    	}
    	if(cnt>4) return;
    	for(int i=1;i<=cnt;i++){
    		for(int j=i+1;j<=cnt;j++){
    			for(int k=0;k<gap;k++) swap(a[op[i]+k],a[op[j]+k]);
    			dfs(x+1,sum+1);
    			for(int k=0;k<gap;k++) swap(a[op[i]+k],a[op[j]+k]);
    		}
    	}
    }
    int main()
    {
    	m=read();
    	for(i=1;i<=m;i++) n*=2;
    	f[0]=1;
    	for(i=1;i<=12;i++) f[i]=f[i-1]*i;
    	for(i=1;i<=n;i++) a[i]=read();
    	dfs(1,0);
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    windows 11下载地址
    ubuntu 安装无线网卡驱动
    修复因为安装openssl 1.0.0而导致使用ssh和scp产生警告信息
    vim下Java自动补全插件javacomplete
    mysql 配置多实例(mysqld_multi)
    nagios 整合 ganglia 设置邮件、短信报警
    Linux (ubuntu 10.10) 安装两个MySQL
    HDU 3269 P2P File Sharing System
    ubuntu 10.10 安装 sun java
    闲逛计算机系统(一):从HelloWorld说起
  • 原文地址:https://www.cnblogs.com/LSlzf/p/11873089.html
Copyright © 2011-2022 走看看