luoguP3322 [SDOI2015]排序

首先我们可以容易地知道任意区间交换的顺序对答案没有影响.

所以我们可以按照区间的长度进行搜索.

又因为每一种长度的区间只能交换一次,所以我们可以进行剪枝.

对于当前搜索区间的长度\(2^x\),我们可以对于每一个长度为\(2^{x+1}\)的区间,判断它是不是单调递增且相邻两数之间差\(1\),如果不是,则打上标记.

如果被标记了的区间超过了\(2\),那么无论如何都不能排好序了,直接回溯(想一想,为什么).

下面我们分标记区间的个数情况讨论:

  • \(0\)个:直接进行下一层的搜索.
  • \(1\)个: 直接将区间砍成两半,前面与后面交换就好了.

    原因很简单:上一层的区间一定都满足了要求,即单调递增且相邻两数之间差\(1\),所以只要交换前后两段即可(可以运用数学归纳法的思想).

  • \(2\)个:分别考虑两个区间头头交换,头尾交换,尾头交换,尾尾交换四种情况,若可行则继续搜索.

将每次可行的答案取阶乘累加至\(ans\)即可.

#include<bits/stdc++.h>
#define il inline
#define rg register
#define gi read<int>
using namespace std;
const int O = 1 << 12 | 15;
template<class TT>
il TT read() {
    TT o = 0,fl = 1; char ch = getchar();
    while (!isdigit(ch) && ch != '-') ch = getchar();
    if (ch == '-') fl = -1, ch = getchar();
    while (isdigit(ch)) o = o * 10 + ch - '0', ch = getchar();
    return fl * o;
}
int n, a[O], anc[15][O], ans;
il void swapp(int & x, int & y) {x ^= y ^= x ^= y;}
il void Swap(int S1, int S2, int x) {
    for (int i = 0; i < 1 << x - 1; ++i)
        swapp(anc[x + 1][S1 + i], anc[x + 1][S2 + i]);
}
il void dfs(int, int);
il void Doit(int S1, int S2, int b1, int b2, int x, int num) {
    Swap(S1, S2, x);
    for (int i = 1; i < 1 << x; ++i) {
        if (anc[x + 1][b1 + i] - anc[x + 1][b1] != i) return Swap(S1, S2, x);
        if (anc[x + 1][b2 + i] - anc[x + 1][b2] != i) return Swap(S1, S2, x);
    }
    dfs(x + 1, num + 1);
    Swap(S1, S2, x);
}
il void dfs(int x, int num) {
    if (x == n + 1) {
        for (int i = 1; i <= 1 << n; ++i) if (anc[x][i] ^ i) return ;
        int res = 1;
        for (int j = 2; j <= num; ++j) res *= j;
        ans += res;
        return ;
    }
    int cnt = 0, S1, S2;
    for (int i = 1; i <= 1 << n; i += 1 << x)
        for (int j = 1; j < 1 << x; ++j)
            if (anc[x][i + j] != anc[x][i] + j) {
                if (cnt == 2) return ;
                if (cnt) S2 = i;
                else S1 = i;
                ++cnt; break;
            }
    for (int i = 1; i <= 1 << n; ++i) anc[x + 1][i] = anc[x][i];
    if (!cnt) return dfs(x + 1, num);
    if (cnt == 1) {
        Swap(S1, S1 + (1 << x - 1), x);
        dfs(x + 1, num + 1);
        return ;
    }
    Doit(S1, S2, S1, S2, x, num); Doit(S1 + (1 << x - 1), S2 + (1 << x - 1), S1, S2, x, num);
    Doit(S1, S2 + (1 << x - 1), S1, S2, x, num); Doit(S1 + (1 << x - 1), S2, S1, S2, x, num);
}
int main() {
    n = gi();
    for (int i = 1; i <= 1 << n; ++i) anc[1][i] = gi();
    dfs(1, 0);
    printf("%d\n", ans);
    return 0;
}

原文地址:https://www.cnblogs.com/lylyl/p/11762225.html

时间: 2024-10-28 11:02:09

luoguP3322 [SDOI2015]排序的相关文章

【BZOJ 3990】 [SDOI2015]排序

3990: [SDOI2015]排序 Time Limit: 20 Sec Memory Limit: 128 MB Submit: 152 Solved: 83 [Submit][Status][Discuss] Description 小A有一个1-2^N的排列A[1..2^N],他希望将A数组从小到大排序,小A可以执行的操作有N种,每种操作最多可以执行一次,对于所有的i(1<=i<=N),第i中操作为将序列从左到右划分为2^{N-i+1}段,每段恰好包括2^{i-1}个数,然后整体交换其

[SDOI2015]排序 题解 (搜索)

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]=[

BZOJ3990 SDOI2015 排序 DFS

题意:给定一个长度为2^N的序列和N个操作,每个操作i为将2^N分为2^(N-i+1)段,然后任意交换其中两段,求有多少种不同的交换方案使得序列升序 题解: 由于一个合法的方案中,交换操作的先后顺序,方案依然合法,所以我们只需要确定使用哪些操作. 按i的大小从小到大枚举每一个操作i,然后将序列分为2^(N-i)段,看有多少段不是递增的.如果有两段以上显然无解,如果有一段那么直接折半交换,如果有两段则枚举四种情况看是否合法. #include <cstdio> #include <cstr

bzoj3990[SDOI2015]排序

http://www.lydsy.com/JudgeOnline/problem.php?id=3990 DFS 好吧,表示不会做. 发现对于这些搜索的题我比较弱,看来需要加强一下. 回归正题. 我们发现对于一个操作方案(不妨记操作数为$cnt$),我们任意改变操作的顺序,总可以满足条件. 根据最小表示法的原理,我们规定按照编号从小到大进行操作,如果可行,那么$ans+=cnt!$ 假设我们做到第$i$个操作,此时对于第$1$到第$i-1$个操作,我们已经知道了各个操作是否用到:并且我们保证:如

BZOJ 3990 Sdoi2015 排序 DFS

题目大意:给定一个长度为2^n的排列,有n个操作,第i个操作为[将序列分成2^(n-i+1)段,每段长2^(i-1),然后任选两段交换],每个操作最多用一次,求有多少操作序列能把序列排出来 Orz dzy 首先我们很容易发现一个操作序列是否合法与序列的顺序是无关的 因此我们只需要确定某个操作序列中每个操作选不选就行了 那么这类操作序列对答案的贡献就是选择的操作数的阶乘 我们从小到大DFS,对于第i次操作我们将序列分成2^(n-i)段,每段长度2^i 我们找到序列中不是连续递增的段,如果这样的段超

【搜索】BZOJ 3990: 【Sdoi 2015】排序

3990: [SDOI2015]排序 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 336  Solved: 164[Submit][Status][Discuss] Description 小A有一个1-2^N的排列A[1..2^N],他希望将A数组从小到大排序,小A可以执行的操作有N种,每种操作最多可以执行一次,对于所有的i(1<=i<=N),第i中操作为将序列从左到右划分为2^{N-i+1}段,每段恰好包括2^{i-1}个数,然后整体交换

【SDOI2015】bzoj3990 排序

A. 排序 题目描述 输入格式 输出格式 一行,一个整数,表示可以将数组A从小到大排序的不同的操作序列的个数. 样例 样例输入 3 7 8 5 6 1 2 4 3 样例输出 6 数据范围与提示 对于30%的数据,1<=N<=4: 对于全部的数据,1<=N<=12. 一个伪装成计数题的大搜索… 首先要知道一个结论:对于一个操作序列,如果他是合法的,那么他的全排列都是合法的,所以从小到大搜索,同时记录操作数k. 对于第x次操作,把序列分成2x-1段,暴力搜索有几段不满足a[i]=a[i

【SDOI2015第1轮第1试】排序

#include<cstdio> #include<iostream> using namespace std; typedef long long LL; const int N = 1 << 13; int n , a[N]; LL ans , fac[13]; inline bool check(int len) { for(register int i = 1; i <= 1 << n - len; i++) if (a[(i - 1) * (

【BZOJ】【3991】【SDOI2015】寻宝游戏

dfs序 我哭啊……这题在考试的时候(我不是山东的,CH大法吼)没想出来……只写了50分的暴力QAQ 而且苦逼的写的比正解还长……我骗点分容易吗QAQ 骗分做法: 1.$n,m\leq 1000$: 直接找一个关键点做根进行深搜,算出其他关键点都与root连通的最小边权和,再×2 2.一条链的情况:用set维护序列中哪些点选了,然后ans=(sum[tail]-sum[head])*2; 其中sum[i]表示从序列首到第 i 个的边长之和……嗯就是前缀和优化一下= =取首和尾这一段的Len之和