E. Permutation Sorting 树状数组实现离线区间数点
题目链接🔗
题意解读:给定一串数组a,每次操作将所有的 a[i] != i
的数循环右移一位,直到所有的数都在自己的位置上。求对于1到n之间的每个i,需要移动多少次。
首先,先考虑移动次数的问题:
为了简化循环问题,考虑将数组长度手动扩充至 2 * n,对于所有的位置 i 上的一个 a[i] ,分两种情况
a[i] >= i
,移动完后依然在n内a[i] < i
,这种情况下右移后会超出原来的边界n
这里使用 from 数组来存储下标 i 时对应的起点 from[i] ,i的含义即为终点的标号(后续遍历的时候从小到大也就间接实现了排序的效果)
从小到大开始枚举终点,找到所有从起点到终点内经过的点 j ,如果满足 from[i] < from[j] < j < i
,那么对应的 i 需要走的次数就减一。
因为终点已经排序,在每次向后枚举的过程中一定可以保证是判断了中间经历点的所有情况,因此做法可行。
最后,在遍历的时候,任意之前走过的 j 的终点一定是满足条件的,因此还需要使用树状数组来维护一个已经走过的区间的起点。
#include <bits/stdc++.h>
using namespace std;
template <typename T>
struct Fenwick {
int n;
std::vector<T> tree;
Fenwick(int n_ = 0) : n(n_) {
tree.assign(n + 1, T());
}
void add(int i, T v) {
for (; i <= n; i += i & -i) {
tree[i] += v;
}
}
T sum(int i) {
auto res = T();
for (; i; i -= i & -i) {
res += tree[i];
}
return res;
}
T rangeSum(int l, int r) {
return sum(r) - sum(l - 1);
}
};
void solve()
{
int n, ed;
cin >> n;
vector<int> from(2 * n + 1), ans(n);
for (int st = 1; st <= n; st++) {
cin >> ed;
if (st <= ed) {
from[ed] = st;
// 细节:对于横跨了n的点计算不动点区间数的时候,也需要没有横跨n的点的数据,因此再加一份在后面
from[ed + n] = st + n;
} else {
from[ed + n] = st;
}
}
Fenwick<int> fen(2 * n);
for (int ed = 1; ed <= 2 * n; ed++) {
if (!from[ed]) {
continue;
}
if (from[ed] <= n) {
// (ed - 1) % n 表示将区间的终点映射在 0 到 n - 1 的区间内,方便输出答案
// ed - from[ed] 表示不考虑其他不动点的情况下需要的次数
// rangeSum(from[ed] + 1, ed - 1) 找到所有的满足 from[i] < from[j] < j < i的区间个数
// 二者相减即是答案
ans[(ed - 1) % n] = ed - from[ed] - fen.rangeSum(from[ed] + 1, ed - 1);
}
fen.add(from[ed], 1);
}
for (int i = 0; i < n; i++) {
cout << ans[i] << " \n"[i == n - 1];
}
}
int main()
{
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】