【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}个数,然后整体交换其中两段.小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].

Input

第一行,一个整数N

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

Output

一个整数表示答案

Sample Input

3

7 8 5 6 1 2 4 3

Sample Output

6

HINT

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

Source

Round 1 感谢ZKY制作非官方数据

思路题+爆搜。

可以发现使用操作的顺序对答案是没有影响的,那么我们可以就可以从i较小的开始枚举,最后计算对答案的贡献的时候阶乘一下即可。

从小到大枚举i的话会有一个神奇的性质:

在i操作的时候分成的每段都是2i?1个数字,那么我们先分每段为2i个数字,如果有超过两段不是从小到大且连续的数字,就无解。

因为每次交换最多只能使得两段有序。

所以dfs只要枚举如何交换即可。

(注意在完成i操作的dfs之后,划分出的长度为2i段都是递增且连续的,因为在i+1及之后的操作中都只能交换更大的块,所以小块必须在前面的交换中有序)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#define LL long long
using namespace std;
LL ans=0,fac[20];
int v[100],a[100005],n,N;
int ok(int x,int y)
{
    for (int i=x+1;i<=y;i++)
        if (a[i]!=a[i-1]+1)
            return 0;
    return 1;
}
void Swap(int x,int y,int l)
{
    for (int i=0;i<l;i++)
        swap(a[x+i],a[y+i]);
}
void dfs(int i,int cnt)
{
    if (i==n)
    {
        ans+=fac[cnt];
        return;
    }
    int b[5],tot=0;
    for (int j=0;j<N;j+=(1<<(i+1)))
        if (!ok(j,j+(1<<(i+1))-1))
        {
            if (tot==4) return;
            b[++tot]=j,b[++tot]=j+(1<<i);
        }
    if (!tot) dfs(i+1,cnt);
    if (tot==2)
    {
        if (a[b[2]]+(1<<i)==a[b[1]])
        {
            Swap(b[1],b[2],1<<i);
            dfs(i+1,cnt+1);
            Swap(b[1],b[2],1<<i);
        }
    }
    if (tot==4)
    {
        if (a[b[3]]+(1<<i)==a[b[2]]&&a[b[1]]+(1<<i)==a[b[4]])
        {
            Swap(b[1],b[3],1<<i);
            dfs(i+1,cnt+1);
            Swap(b[1],b[3],1<<i);
        }
        if (a[b[3]]+(1<<i)==a[b[1]]&&a[b[4]]+(1<<i)==a[b[2]])
        {
            Swap(b[1],b[4],1<<i);
            dfs(i+1,cnt+1);
            Swap(b[1],b[4],1<<i);
        }
        if (a[b[2]]+(1<<i)==a[b[4]]&&a[b[1]]+(1<<i)==a[b[3]])
        {
            Swap(b[2],b[3],1<<i);
            dfs(i+1,cnt+1);
            Swap(b[2],b[3],1<<i);
        }
        if (a[b[1]]+(1<<i)==a[b[4]]&&a[b[3]]+(1<<i)==a[b[2]])
        {
            Swap(b[2],b[4],1<<i);
            dfs(i+1,cnt+1);
            Swap(b[2],b[4],1<<i);
        }
    }
}
int main()
{
    cin>>n;
    fac[0]=1;
    for (int i=1;i<=n;i++)
        fac[i]=1LL*fac[i-1]*i;
    N=1<<n;
    for (int i=0;i<N;i++)
        scanf("%d",&a[i]);
    dfs(0,0);
    cout<<ans<<endl;
    return 0;
}

时间: 2024-11-17 18:53:18

【BZOJ 3990】 [SDOI2015]排序的相关文章

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}个数,然后整体交换

BZOJ 3990 [SDOI 2015] 排序 解题报告

这个题哎呀...细节超级多... 首先,我猜了一个结论.如果有一种排序方案是可行的,假设这个方案是 $S$ . 那么我们把 $S$ 给任意重新排列之后,也必然可以构造出一组合法方案来. 于是我们就可以 $O(2^n)$ 枚举每个操作进不进行,再去判断,如果可行就 $ans$ += $|S|!$. 然而怎么判断呢? 我们按照操作种类从小到大操作. 假设我们现在在决策第 $i$ 种操作并且保证之前之后不需要进行种类编号 $< i$ 的操作. 那么我们只考虑那些位置在 $2^i+1$ 的位置的那些数.

BZOJ 3994: [SDOI2015]约数个数和

3994: [SDOI2015]约数个数和 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 898  Solved: 619[Submit][Status][Discuss] Description 设d(x)为x的约数个数,给定N.M,求   Input 输入文件包含多组测试数据. 第一行,一个整数T,表示测试数据的组数. 接下来的T行,每行两个整数N.M. Output T行,每行一个整数,表示你所求的答案. Sample Input 2 7

BZOJ 3992: [SDOI2015]序列统计 NTT+快速幂

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1155  Solved: 532[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 466[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

BZOJ 3995 Sdoi2015 道路修建 线段树

题目大意:给定一个2*n的网格图,多次改变某条边的权值或询问y坐标在[l,r]中的2*(r-l+1)个点的MST 这真是一道好题= = 我们用线段树维护每个区间内的MST 然后考虑合并 合并两个区间 我们会加入两条边 这样一定会形成一个环 切掉环上最大边 这题没了 然后就是一坨乱七八糟的细节讨论= = 首先最大边一定在图中的彩色部分内 绿色部分可以O(1)求 我们需要维护的是红色和蓝色部分 然后如果切掉的边是横边或者切掉的竖边不是区间唯一的竖边(如图中蓝色竖边) 那么红框和蓝框直接用左右区间的即

bzoj3990[SDOI2015]排序

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

[BZOJ 3995] [SDOI2015] 道路修建 【线段树维护连通性】

题目链接:BZOJ - 3995 题目分析 这道题..是我悲伤的回忆.. 线段树维护连通性,与 BZOJ-1018 类似,然而我省选之前并没有做过  1018,即使它在 ProblemSet 的第一页. 更悲伤的是,这道题有 40 分的暴力分,写个 Kruskal 就可以得到,然而我写了个更快的 DP . 这本来没有什么问题,然而我的 DP 转移少些了一种情况,于是...爆零.没错,省选前20名可能就我没有得到这 40 分? 不想再多说什么了...希望以后不要再这样 SB 了,如果以后还有机会的