【CSP2019 模拟赛】Time

题目描述:

        小 A 现在有一个长度为 𝑛 的序列 {𝑥𝑖},但是小 A 认为这个序列不够优美。 小 A 认为一个序列是优美的,当且仅当存在 𝑘 ∈ [1, 𝑛],满足: 𝑥1 ≤ 𝑥2 ≤ … ≤ 𝑥𝑘 ≥ 𝑥𝑘+1 ≥ … ≥ 𝑥𝑛 。现在小 A 可以进行若干次操作,每次可以交换序列中相邻的两个项,现在他想知道最少操作多少次之后能够使序列变为优美的。

Input Format

第一行一个正整数 𝑛,表示序列的长度。 接下来一行 𝑛 个整数,表示初始的序列。

Output Format

输出一行一个整数,表示最少需要的操作次数。

Sample Input

5 3 4 1 2

Sample Output

1

Constraints

对于 30% 的数据,𝑛 ≤ 12。

对于 60% 的数据,𝑛 ≤ 100000, 𝑎𝑖 互不相同。

对于 100% 的数据,𝑛, 𝑎𝑖 ≤ 100000。

思路:

        仔细分析题意,因只能交换序列中相邻的两个项,且要求中间数大,两端较小。可以用贪心思想将每一个小的x[i]往左或往右两端移动,取最小的交换次数,最后累加即为所求。其实就是计算每个数左边或右边比它大的数(逆序对)有多少个,取最优。

        用树状数组刚好能满足快速计算逆序对的需求。注意:因数据有可能重复,我们需要将数组从大到小排序,且将数值相同的元素id大的往前放。

#include<bits/stdc++.h>
using namespace std;
int t[100005], n;
struct node {
	int x, id;
} a[100005], b[100005];
bool sort1(node a, node b) { //从大到小排序,值相同ID大的放前面
	if (a.x == b.x) return a.id > b.id;
	return a.x > b.x;
}
void add(int pos, int x) {
	while (pos <= n) {
		t[pos] += x;
		pos += -pos & pos;
	}
}
int sum(int pos) {
	int ans = 0;
	while (pos) {
		ans += t[pos];
		pos -= -pos & pos;
	}
	return ans;
}
int main() {
	int ans = 0;
	cin >> n;
	int resa[n + 1], resb[n + 1];
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i].x);
		b[i].x = a[i].x;	//复制一个数组用于计算右边逆序对
		a[i].id = i;	//求i左边逆序对
		b[i].id = n - i + 1; //求i右边逆序对,id取反
	}
	sort(a + 1, a + 1 + n, sort1);
	sort(b + 1, b + 1 + n, sort1);
	for (int i = 1; i <= n; i++) {
		add(a[i].id, 1);
		resa[a[i].id] = sum(a[i].id - 1); //把每个i的逆序对保存到数组对应位置
	}
	memset(t, 0, sizeof(t)); //清空数组,以便计算右边逆序对
	for (int i = 1; i <= n; i++) {
		add(b[i].id, 1);
		resb[b[i].id] = sum(b[i].id - 1); 
	}
	reverse(resb + 1, resb + 1 + n); //之前id是反向定义,需要反转数组元素
	for (int i = 1; i <= n; i++)
		ans += min(resa[i], resb[i]); //取每个i对于左右逆序对的最小值的和,即为所求
	cout << ans;
	return 0;
}

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值