「题目描述」
小A 有一个 $1-2^N$ 的排列 $A[1 \dots 2^N]$ ,他希望将 $A$ 数组从小到大排序。
小A 可以执行的操作有 $N$ 种,每种操作最多可以执行一次,对于所有的 $i(1 \leq i \leq N)$,第 $i$ 种操作为将序列从左到右划分为 $2^{N-i+1}$ 段,每段恰好包括 $2^{i-1}$个数,然后整体交换其中两段。
小A 想知道可以将数组 $A$ 从小到大排序的不同的操作序列有多少个,小A 认为两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同)。
下面是一个操作事例:
$N=3, A[1 \dots 8] = [3, 6, 1, 2, 7, 8, 5, 4]$
第一次操作,执行第 3 种操作,交换 $A[1 \dots 4]$ 和 $A[5 \dots 8]$,交换后的 $A[1 \dots 8]$为 $[7,8,5,4,3,6,1,2]$ 。
第二次操作,执行第 1 种操作,交换 $A[3]$ 和 $A[5]$ ,交换后的 $A[1 \dots 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] $。
「题目链接」
BZOJ 3990 排序 「SDOI 2015」
「 解题思路」
首先我们~~(根本不能)~~发现:操作序列中顺序是不重要的。
直观理解的话,就是各项操作交换的块是完全包含或完全分离的,所以不会出现进行一个操作导致另一个操作无法进行的情况。
这样的话,我们就可以对操作序列 定序,然后贡献乘上长度的阶乘就可以了。
我们按从小到大考虑 $1 \dots N$ 这些操作,进行 $DFS$。
我们知道,第 $i$ 次操作只能交换长度为 $2^{i - 1}$ 的块,那么为了保证最后有序,在这次交换以前,所有块的 内部 必须是有序的,不然方案必定不合法,因为之后的操作没法改变这个大小的块 内部 的顺序。
所以要在 $DFS$ 的过程中保证这一点:在第 $i$ 次交换后,所有长度为 $2^{i}$ 的块内部有序。
因为一次交换最多改变两个块的有序状态,所以最多将两个不合法的块交换成合法。
每次 $DFS$ 时先扫描整个序列,记录不合法块的位置和数量。如果其数量为 1,则尝试交换该块的两半;数量为 2,则尝试四个 半个块 两两交换,共 4 种情况;如果数量大于 2,则这种情况下无解。
复杂度粗看是 $O(n4^n)$,但其实 4 种情况种最多 1 种成立,所以其实是 $O(n^2)$。
总结:自己没有想到定序,以后遇到这种天然带有顺序的操作要尝试想一下操作顺序的影响。
「AC 代码」
#include <iostream>
#include <vector>
const int MAXN = 12;
int a[1 << MAXN];
int pow[MAXN + 1], fact[MAXN + 1];
int n;
inline void init(){
pow[0] = 1;
for(int i = 1; i <= MAXN; i++) pow[i] = pow[i - 1] << 1;
fact[0] = 1;
for(int i = 1; i <= MAXN; i++) fact[i] = fact[i - 1] * i;
}
inline bool check(int begin, int len){
for(int i = 1; i < len; i++){
if(a[begin + i] != a[begin + i - 1] + 1) return false;
}
return true;
}
inline void swap(int x, int y, int len){
for(int i = 0; i < len; i++) std::swap(a[x + i], a[y + i]);
}
inline void find(int len, std::vector<int> &vec){
for(int i = 0; i < pow[n]; i += len){
if(!check(i, len)){
vec.push_back(i);
if(vec.size() > 2) return;
}
}
}
long long ans;
inline void dfs(int i, int cnt){
if(i == n + 1){
ans += fact[cnt];
} else{
int len = pow[i - 1];
std::vector<int> vec;
find(2 * len, vec);
if(vec.empty()){
dfs(i + 1, cnt);
} else if(vec.size() == 1){
int x = vec[0];
swap(x, x + len, len);
if(check(x, 2 * len)) dfs(i + 1, cnt + 1);
swap(x, x + len, len);
} else if(vec.size() == 2){
int x = vec[0], y = vec[1];
for(int a = 0; a <= 1; a++) for(int b = 0; b <= 1; b++){
int xi = x + a * len, yi = y + b * len;
swap(xi, yi, len);
if(check(x, len * 2) && check(y, len * 2)) dfs(i + 1, cnt + 1);
swap(xi, yi, len);
}
}
}
}
inline long long solve(){
ans = 0;
dfs(1, 0);
return ans;
}
int main(){
init();
std::cin >> n;
for(int i = 0; i < pow[n]; i++) std::cin >> a[i];
std::cout << solve() << std::endl;
return 0;
}
就是这样啦~