zoukankan      html  css  js  c++  java
  • BZOJ3990 [SDOI2015]排序 【搜索】

    题目

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

    输入格式

    第一行,一个整数N

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

    输出格式

    一个整数表示答案

    输入样例

    3

    7 8 5 6 1 2 4 3

    输出样例

    6

    提示

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

    题解

    我们可以大力猜想出操作的顺序可以是任意的
    随便试试就会发现
    仔细观察发现,所有的操作区间只有 完全包含关系 or 不相交
    对于一个合法操作序列中的相邻两个操作(a)(b),我们尝试交换其顺序
    ①若(a)(b)不相交,那么显然无影响
    ②若(a)(b)相交,那么一定是包含关系,不妨设(|a| < |b|)
    如果(a)的一个操作区间在(b)中,那么在操作(b)前后交换(a)中的两个区间显然不改变顺序
    如果(a)的两个操作区间都在(b)中,那么在(b)操作前后这两个区间内的元素是不变的,我们只需在(b)操作之后找到原来的两个区间进行交换,最后的序列仍然不变
    这就粗略地证明了

    既然顺序无关,我们就可以从小枚举了
    因为大区间操作无法影响其内部,所以我们每一次操作都要保证下一级区间内部一定是按+1递增顺序的
    具体地,对于第(i)种操作,操作区间长度为(2^{i - 1}),那么我们找到第(i + 1)种操作的所有区间,如果其内部不是按+1递增的,那么这个区间一定要被操作

    如果这样的区间数量(>3),显然我们是无法全部顾及的,直接返回
    如果这样的区间数量为1,那么只需要交换这个区间内部
    如果这样的区间数量为2,若存在合法方案,一定是交换这两个区间(2)个子区间的一个,共有(4)中情况,逐一检验即可

    最后,如果一个操作集合(S)合法,那么将贡献(|S|!)的方案数

    如果说每一层只会有一种情况合法的话,总的复杂度(O(n * 2^n))

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
    using namespace std;
    const int maxn = 13,maxm = 10000,INF = 1000000000;
    inline int read(){
    	int out = 0,flag = 1; char c = getchar();
    	while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    	while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    	return out * flag;
    }
    int a[maxm],n,N,chs;
    LL ans,fac[maxn];
    bool isorder(int l,int len){
    	for (int i = 1; i < len; i++) if (a[l + i] != a[l + i - 1] + 1) return false;
    	return true;
    }
    void Swap(int u,int v,int len){
    	for (int i = 0; i < len; i++) swap(a[u + i],a[v + i]);
    }
    void dfs(int dep){
    	if (dep > n){
    		if (isorder(1,N)) ans += fac[chs];
    		return;
    	}
    	int len = 1 << dep,x = 0,y = 0;
    	for (int i = 1; i <= N; i += len){
    		if (!isorder(i,len)){
    			if (!x) x = i;
    			else if (!y) y = i;
    			else return;
    		}
    	}
    	if (!x && !y) dfs(dep + 1);
    	else if (x && !y){
    		chs++;
    		Swap(x,x + (len >> 1),(len >> 1));
    		dfs(dep + 1);
    		Swap(x,x + (len >> 1),(len >> 1));
    		chs--;
    	}
    	else if (x && y){
    		chs++;
    		for (int i = 0; i < 2; i++)
    			for (int j = 0; j < 2; j++){
    				Swap(x + i * (len >> 1),y + j * (len >> 1),(len >> 1));
    				if (isorder(x,len) && isorder(y,len))
    					dfs(dep + 1);
    				Swap(x + i * (len >> 1),y + j * (len >> 1),(len >> 1));
    		}
    		chs--;
    	}
    }
    int main(){
    	fac[0] = 1;
    	for (int i = 1; i <= 12; i++) fac[i] = fac[i - 1] * i;
    	n = read(); N = (1 << n);
    	REP(i,N) a[i] = read();
    	dfs(1);
    	cout << ans << endl;
    	return 0;
    }
    
    
  • 相关阅读:
    ES6/5比较
    Javascript中的var和let
    git中remotes/origin/HEAD指向的分支丢失
    js实用篇之数组、字符串常用方法
    JS设计模式一:单例模式
    Linux C 面试题总结 .
    深入理解javascript原型和闭包(15)——闭包
    一些有意思的面试题(持续更新) .C语言编程技巧札记
    一个三流大学生的北京三年 .
    C 字节对齐.我的算法学习之路
  • 原文地址:https://www.cnblogs.com/Mychael/p/8855680.html
Copyright © 2011-2022 走看看