[USACO18OPEN]Out of Sorts P

好题啊……刚了一个上午无果。中午睡觉又想了一个小时。然后下午来操作了一个小时。最终搞定。

题目

传送门 to luogu

思路

这个分割点到底啥子意思嘛?就是两边分别有序的时候,整个数组就有序了。

所以我们发现 分割点屁用没有。不按照分割点将数组割开,直接在原数组上进行更多的几轮冒泡,得到的结果和递归版是一样的。因为 分割点两边的元素不可能交换

但是一旦区间划分至大小为 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 ,需要被冒泡几次。分三种大小关系讨论。

  1. 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 啦。

  1. a x a_x ax 大的,如果在左边,需要等待。若 [ 1 , x ] [1,x] [1,x] x x x 是第 k k k 大,则需要等待 k − 1 k-1 k1 轮。

说白了就是 x x x 作为逆序对的后一个的数量。因为这 k − 1 k-1 k1 个严格大于 x x x 的数会跨越它。

  1. 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值