[bzoj3990][SDOI2015]排序-搜索

Brief 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认为两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同).

Algorithm Design

首先不难发现操作顺序不影响答案, 我们只需要考察每种操作是否选中, 若选中交换哪两块就好了. 一个合法的操作序列如果有(n)个操作, 那么可以给答案(contribute n!). 我们从小到大考察每一种操作, 首先, 可以知道, 对于操作(2^i), 序列肯定已经被分成了(2^{n-i+1})个有序数列, 我们首先检查是否有序, 如果有问题直接(return). 然后扫描每个块, 每两个块都必须是有序的, 否则要交换. 如果(tot geqslant 4)那么一定不合法. 代码表达的非常清楚.

Code

#include <algorithm>
#include <cstdio>
#define ll long long
const int maxn = 1 << 13;
int n;
int a[maxn];
ll po[13];
ll ans;
bool check(int k) {
  for (int i = 1; i <= (1 << (n - k)); i++)
    if (a[(i - 1) * (1 << k) + 1] + (1 << (k - 1)) !=
        a[(i - 1) * (1 << k) + (1 << (k - 1)) + 1])
      return 0;
  return 1;
}
void swap(int i, int j, int k) {
  for (int m = 1; m <= k; m++)
    std::swap(a[i + m - 1], a[j + m - 1]);
}
void dfs(int now, int num) {
  if (now && !check(now))
    return;
  if (now == n) {
    ans += po[num];
    return;
  }
  dfs(now + 1, num);
  int tmp[5], tot = 0;
  for (int i = 1; i <= (1 << (n - now)); i += 2)
    if (a[i * (1 << now) + 1] != a[(i - 1) * (1 << now) + 1] + (1 << now)) {
      if (tot == 4)
        return;
      tmp[++tot] = i;
      tmp[++tot] = i + 1;
    }
  if (!tot)
    return;
  for (int i = 1; i <= tot; i++)
    for (int j = i + 1; j <= tot; j++) {
      swap((1 << now) * (tmp[i] - 1) + 1, (1 << now) * (tmp[j] - 1) + 1,
           1 << now);
      dfs(now + 1, num + 1);
      swap((1 << now) * (tmp[i] - 1) + 1, (1 << now) * (tmp[j] - 1) + 1,
           1 << now);
    }
}
int main() {
  // freopen("input", "r", stdin);
  po[0] = 1;
  for (int i = 1; i <= 12; i++)
    po[i] = po[i - 1] * i;
  scanf("%d", &n);
  for (int i = 1; i <= 1 << n; i++)
    scanf("%d", &a[i]);
  dfs(0, 0);
  printf("%lld", ans);
}
原文地址:https://www.cnblogs.com/gengchen/p/6593071.html