好题啊……刚了一个上午无果。中午睡觉又想了一个小时。然后下午来操作了一个小时。最终搞定。
题目
思路
这个分割点到底啥子意思嘛?就是两边分别有序的时候,整个数组就有序了。
所以我们发现 分割点屁用没有。不按照分割点将数组割开,直接在原数组上进行更多的几轮冒泡,得到的结果和递归版是一样的。因为 分割点两边的元素不可能交换。
但是一旦区间划分至大小为 1 1 1,事情就不一样了,量变引起了质变,它不再提供 w o r k c o u n t e r \tt work\; counter workcounter 的贡献了。在被割成单个元素之前,每冒泡一次,就会为 w o r k c o u n t e r \tt work\; counter workcounter 提供 1 1 1 的贡献。
所以问题变成了,什么时候被划分成了单个元素。而划分成单个元素的意义就是,走到了正确的位置上。
我们考虑第 x x x 个数,也就是 a x a_x ax ,需要被冒泡几次。分三种大小关系讨论。
- 比 a x a_x ax 小的,如果在右边,需要跨越。设其为 a y a_y ay 。若下标在 [ 1 , y ] [1,y] [1,y] 内的元素中 a x a_x ax 是第 k k k 大,则需要 k k k 次。
这是因为在 a y a_y ay 前面有比它大的数字时,每一轮冒泡必然有且仅有一个比它大的数字越过它。有点像这道题,都是对冒泡排序过程的理解。
显然只需要求最大的 y y y 的限制。如果我们从小到大处理 a a a,就很容易找到这个 y y y 啦。
- 比 a x a_x ax 大的,如果在左边,需要等待。若 [ 1 , x ] [1,x] [1,x] 中 x x x 是第 k k k 大,则需要等待 k − 1 k-1 k−1 轮。
说白了就是 x x x 作为逆序对的后一个的数量。因为这 k − 1 k-1 k−1 个严格大于 x x x 的数会跨越它。
- 与 a x a_x ax 相等的呢?可以在离散化的时候去掉,因为冒泡是稳定的,可以想象成靠前的 v v v 小、靠后的 v v v 大,就不会发生交换。
这些都可以用树状数组解决。复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
如果你的实现想偷懒,你还可以把前两种情况合在一起,因为二者都是求 a x a_x ax 的排名,无非就是 max ( x , y ) \max(x,y) max(x,y) 前缀求排名。
代码
温馨提示:第一轮要手动处理,因为第一轮就走到了正确位置也没用,仍然会产生贡献。
或者有更简单的方法,就是认为每个点至少要用 1 1 1 轮走到正确位置。我就是用的这招。
代码中 r r r 就是那个想要的 y y y,枚举的变量 i d [ i ] id[i] id[i] 就是正在处理的 x x x 。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 100005;
int a[MaxN], c[MaxN];
bool cmp(const int &x,const int &y){
if(a[x] != a[y])
return a[x] < a[y];
return x < y; // stable
}
int id[MaxN];
int main(){
int n = readint();
rep(i,1,n) a[id[i] = i] = readint();
sort(id+1,id+n+1,cmp);
rep(i,1,n) c[i] = (i&-i);
int_ ans = 0; int now = 0;
for(int i=1,r=0; i<=n; ++i){
r = max(r,id[i]); now = 0;
for(int j=r; j; j&=(j-1))
now += c[j]; // count 1
if(r == id[i]) -- now;
ans += max(now,1); // at least 1
for(int j=i; j<=n; j+=(j&-j))
-- c[j]; // from 1 to 0
}
printf("%lld\n",ans);
return 0;
}