AT_abc413_e题解
AtCoder413 E - Reverse 2^i 题解
题目传送门
vjudge(+中文翻译) Reverse 2^i - AtCoder abc413_e
AtCoder413 E - Reverse 2^i
题面
这道题给了我们一个数组 \(P\) ,长度为 \(2^N\) ,题目保证 \(P\) 是 \((1,2,3,...,2^N)\) 的一个排列。
我们可以做以下操作任意次:
- 选择一个整数 \(b\) \((0 \leq b \leq N)\),将 \(P\) 从左往右每 \(2^b\) 个数字分成一段,然后任意选择一段进行左右翻转。
输出能获得的所有新的 \(P\) 数组中字典序最小的那个。
思路
思路1
思路1-思路
首先观察到:对于任意一个区间,都可以将最小值放到最前面。但是后面的次小值可能无法放到第二位,因为移动的时候会带动其他的数字,次小值如果放到第二位,可能会影响最小值的位置。
所以我们要避免出现影响。
明显:只要翻转的区间不盖到这个位置即可。
如果看最大的区间,那么我们的思路就是:先将最小值放到这个区间最前面,然后去考虑那些不会影响到最小值位置的区间(注意:我们考虑的区间长度必须是 \(2^k\) 且将原数组从左往右每 \(2^k\) 个数字分成一段,该区间正好占一段)
而对于小区间,我们发现每次讨论的问题是相同的,递归即可。
思路1-方法:
对于每个区间
1、找到这个区间中最小值的下标
2、我们有一个右指针,最开始右指针指着区间右端,如果这个最小值的下标更靠近右指针,那么将整个区间左端到右指针的区间翻转,使得最小值的下标靠近区间左端,然后将右指针改为原本左指针和右指针的中间。
3、重复2直到右指针和左指针都在区间左端,那么此时最小值也在区间左端。
4、重置右指针为区间右端,每次找到区间左端和右指针的中间,记为 \(t\) ,递归处理 \(t+1\) 到 右指针 的区间,然后将右指针改为t
5、重复4直到右指针与区间左端的中间为区间左端。
思路1-代码:
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int p[(1 << 18) + 1];
void dfs(int l, int r)
{
int t = 1;
// 第1步,找到区间最小值的下标
int mn = 1e9, mnid = 0;
for (int i = l; i <= r; i++)
{
if (p[i] < mn)
{
mn = p[i];
mnid = i;
}
}
// 第2到3步,t表示左端和右指针的中间。
t = (r + l) / 2;
int ls = r;
while (ls != l)
{
if (mnid > t) // 最小值在中点右边
{
reverse(p + l, p + ls + 1);
mnid = l + (ls - mnid); // 注意最小值位置也要跟着翻转
}
ls = t;
t = (l + t) / 2;
}
// 第4到5步,继续考虑较小的区间
t = (r + l) / 2;
ls = r;
while (t != l)
{
dfs(t + 1, ls);
ls = t;
t = (l + t) / 2;
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= (1 << n); i++)
{
cin >> p[i];
}
dfs(1, (1 << n));
for (int i = 1; i <= (1 << n); i++)
{
cout << p[i] << ' ';
}
cout << endl;
return;
}
int main()
{
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
思路2
思路2-思路
对于任何区间,翻转两次等于不翻,所以最多翻一次。
并且显然只有区间最小值在右边的时候翻一次。
对于每个区间的最小值,他移到左半边后,一定是这个区间的左半边的最小值,所以它最终会移到这个区间所属位置的最左边。
而对于右半边,也会找出最小值并放到右半边所属位置的最左边,并且右半边翻转影响不到左半边,左半边翻转影响不到右半边。
每一个区间都会这样,直到最后,就可以找到最小的字典序了。
所以对于某个区间我们只需要找到最小值的位置,如果最小值在这个区间的右半边,则翻转一次,否则不翻转,然后使用递归继续分开考虑左半边和右半边。
思路2-方法:
对于每个区间
1、找到这个区间中最小值的下标
2、找出这个区间的中心点。
3、如果这个最小值更靠近右端,就翻转这个区间,这样最小值就在左半边了。
4、递归处理左端点到中心点的区间和中心点+1到右端点的区间。
思路2-代码
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int p[(1 << 18) + 1];
void dfs(int l, int r)
{
// 退出判断
if (l == r) return;
// 第1步,找到最小值下标
int mn = 1e9, mnid = 0;
for (int i = l; i <= r; i++)
{
if (p[i] < mn)
{
mn = p[i];
mnid = i;
}
}
int t = (l + r) / 2; // 第2步,找中点
if (mnid > t) // 第3步,如果最小值靠近右端,则翻转区间。
{
reverse(p + l, p + r + 1);
}
dfs(l, t); // 考虑左半边
dfs(t + 1, r); // 考虑右半边
}
void solve()
{
cin >> n;
for (int i = 1; i <= (1 << n); i++)
{
cin >> p[i];
}
dfs(1, (1 << n));
for (int i = 1; i <= (1 << n); i++)
{
cout << p[i] << ' ';
}
cout << endl;
return;
}
int main()
{
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}

浙公网安备 33010602011771号