【洛谷P4378 [USACO18OPEN] 】Out of Sorts S

作者:letianJOE
题目链接




题目大意

sorted = false
while (not sorted):
   sorted = true
   moo
   for i = 0 to N-2:
      if A[i+1] < A[i]:
         swap A[i], A[i+1]
         sorted = false

维护这段伪代码如果对于序列 { a 1 , a 2 . . . . . . a n − 1 , a n } \{a_1,a_2......a_{n-1},a_n\} {a1,a2......an1,an} 会输出多少个 moo



思路

你需要学习一下离散化,戳这里


伪代码

首先我们要明确一下伪代码的操作。首先,伪代码很明显能看到是一个变形的冒泡排序,那么他就有多次的冒泡操作。

moo 是在每次的冒泡结束后输出,也就是进行了几次冒泡就是几个 moo 。但是这是一个 while,我们还会需要一次让这个代码退出 while ,所以答案还要加一。

所以最后这个代码的操作就是求出需要进行几次冒泡使得序列有序再加一


做法

我们来看一个样例。

5 4 3 2 1 8 7

我们对于这这个样例需要进行四次冒泡,分别为。

4 3 2 1 5 7 8

3 2 1 4 5 7 8

2 1 3 4 5 7 8

1 2 3 4 5 7 8

对于这个样例,我们可以很明显的看到每一次冒泡都会把现序列中不符合冒泡的数交换到他该有的位置,然后从这开始再来进行这个操作。

对于每一个数,它前面无序的数都必须要移动到它的后面。

而且无论别人如何交换,它们的顺序都不会改变。

所以每一次冒泡我们只能改变一个无序的数,那么就至少需要无序的数的个数次冒泡。

对于每一次冒泡他都会同时改变不同的数一个无序的数。

所以最后需要几次冒泡的答案就是这些无序的数的个数的最大值


实现

问题一:

我们考虑用树状数组来解决求无序的数的个数复杂度太高的情况。

对于 b i t i bit_i biti ,我们维护的是区间 ( i − l o w b i t ( i ) , i ] (i-lowbit(i),i] (ilowbit(i),i] 里面的数一共再数组中出现了多少次。

换而言之,树状数组维护的是每个数再数组中出现了几次。

然后我们当算出 x x x 前缀和,那就代表有 x x x 的前缀和个数比 x x x 小,用总的个数减去小的个数就可以求出比 x x x 大的数有多少个,也就是无序的数有多少个。

问题二:

但是如果我们要按照下标维护树状数组,那么数据太大了,树状数组装不下。我们需要先给它离散化一下即可。

问题三:

但是我们不知道前缀和里那些是在要求的那个数的前面,这样就会把一些它后面的数给他加进去。

我们可以正序遍历,然后每一次加入数据到树状数组,那么现在有的数据就是在它的前面的数。


CODE

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define lowbit(x) x&(-x) 
#define int long long
using namespace std;
struct P4378
{
	int num,id;
}a[100005];
int n;
int ans;
int siz[100005];
int bit[100005];
void add(int k,int x)
{
	for(int i=k;i<=n;i+=lowbit(i))
		bit[i]++;
	return ;
}
int ask(int x)
{
	int cnt=0;
	for(int i=x;i>=1;i-=lowbit(i))
		cnt+=bit[i];
	return cnt;
}
bool cmp(P4378 a,P4378 b)
{
	if(a.num==b.num)
		return a.id<b.id;
	return a.num<b.num;
}
main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i].num,a[i].id=i;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
		siz[a[i].id]=i;
	for(int i=1;i<=n;i++)
	{
		add(i,siz[i]);
//		cout<<ask(siz[i])<<"\n";
		ans=max(ans,i-ask(siz[i]));
	}
	cout<<ans+1<<"\n";
	return 0;
}
  • 32
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值