P3322 [SDOI2015]排序 题解
注意这题操作位置不同的意思不是在数组上不同的位置操作,而是两个方案中操作序列上某个位置的操作不同……
首先有个结论,就是如果我们能用 \(x\) 次操作完成排序,那么这 \(x\) 次操作可以任意交换,对答案的贡献就是 \(x!\)。
因此考虑区间 \(i\) 从小到大枚举操作,由于是从小到大,因此必须要保证第 \(i\) 次操作后所有长度为 \(2^i\) 的区间都排了序且相邻两项差值为 1,否则后面的操作肯定不能再影响这个小区间。
考虑 dfs 处理,下面以 \(i=1\) 为例,剩下的情况可以在递归时修改数列变成 \(i=1\)。
接下来考虑什么情况下能够通过一次操作完成上面说的排序,注意到只有以下两种情况且只能有其中一个情况:
- 只有一个区间逆序,剩下的全满足条件。
- 有两个区间顺序,但是这两个区间不满足差值为 1,剩下的全满足条件。
这两个条件都可以 \(O(n)\) 扫一遍判定,如果都不满足这次操作就不用动,都满足那就非法。
如果满足第一个条件,将这个区间内的两个数都交换即可。
如果满足第二个条件,设这两个区间为 \((a+2,a+1),(a,a+3)\)(也只可能是这种情况),此时我们有两种方式,交换 \(a,a+2\) 或者交换 \(a+1,a+3\)。
这样我们得到了一个可以往下递归的数列,至于递归时显然要满足 \(a_i\equiv i\pmod{2}\),因此我们每两个区间合并成一个数,新数的大小就是这个区间内偶数除以 2 或者奇数加一之后除以 2,这样就可以递归了。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P3322 [SDOI2015]排序
Date:2022/10/26
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 20 + 5, MAXS = (1 << 20) + 5;
int n, a[MAXS];
LL ans = 0, fact[MAXN];
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
void Swap(int &x, int &y) { int t = x; x = y; y = t; }
void dfs(int now, int cnt)
{
if (now == n + 1) { ans += fact[cnt]; return ; }
int m = 1 << (n - now + 1), opt = 0, flag = 0, f1 = 0, f2 = 0, p1 = 0, p2 = 0;
for (int i = 1; i <= m; ++i)
if (a[i] % 2 != i % 2) { ++opt; if (f1 == 0) f1 = i; else f2 = i; }
if (opt > 2) return ; if (opt == 2) Swap(a[f1], a[f2]);
for (int i = 1; i <= m; i += 2)
if (a[i] != a[i + 1] - 1) { ++flag; if (p1 == 0) p1 = i; else p2 = i; }
if (opt && flag) return ; if (flag > 2) return ;
if (flag == 0)
{
for (int i = 1; i <= (m >> 1); ++i) a[i] = ((a[(i << 1) - 1] + 1) >> 1);
if (opt) dfs(now + 1, cnt + 1);
else dfs(now + 1, cnt); return ;
}
int tmp[MAXS]; for (int i = 1; i <= m; ++i) tmp[i] = a[i];
Swap(tmp[p1], tmp[p2]); for (int i = 1; i <= (m >> 1); ++i) a[i] = ((tmp[(i << 1) - 1] + 1) >> 1);
dfs(now + 1, cnt + 1); Swap(tmp[p1], tmp[p2]); Swap(tmp[p1 + 1], tmp[p2 + 1]);
for (int i = 1; i <= (m >> 1); ++i) a[i] = ((tmp[(i << 1) - 1] + 1) >> 1);
dfs(now + 1, cnt + 1);
}
int main()
{
n = Read(); for (int i = 1; i <= (1 << n); ++i) a[i] = Read();
fact[0] = 1; for (int i = 1; i <= n; ++i) fact[i] = fact[i - 1] * i;
dfs(1, 0); printf("%lld\n", ans); return 0;
}