原题链接:小朋友排队
题目描述
$n$ 个小朋友站成一排。
现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。
每个小朋友都有一个不高兴的程度。
开始的时候,所有小朋友的不高兴程度都是 $0。$
如果某个小朋友第一次被要求交换,则他的不高兴程度增加 $1$,如果第二次要求他交换,则他的不高兴程度增加 $2$(即不高兴程度为 $3$),依次类推。当要求某个小朋友第 $k$ 次交换时,他的不高兴程度增加 $k$。
请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。
如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。
输入格式
输入的第一行包含一个整数 $n$,表示小朋友的个数。
第二行包含 $n$ 个整数 $H_1,H_2,…,H_n$,分别表示每个小朋友的身高。
输出格式
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
数据范围
$1≤n≤100000,$
$0≤Hi≤1000000$
输入样例1:
3
3 2 1
输出样例1:
9
样例解释:
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。
思路
可以证明,每个小朋友需要交换的次数是前面比它大的数和后面比它小的数,因为如果前面有比它大的数,那么它必定和前面的交换一次,使得前面大的数排到后面,同理可以知道比它小的数一定要和它交换到前面
那么我们求每个小朋友前后比它大/小的数就有两种方法
归并排序
对于合并时,我们都同时算出前面比它大的数和后面比它小的数
时间复杂度
$O(nlogn)$
C++ 代码
#include <iostream>
using namespace std;
#define x first
#define y second
typedef long long LL;
typedef pair<int,int> PII;
const int N = 100010;
PII w[N];
PII temp[N];
LL h[N];
int n;
void merge_sort(int l,int r) {
if(l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);
int i = l, j = mid + 1,k = 0;
while(i <= mid && j <= r) {
if(w[i] <= w[j]) {
h[w[i].y] += j - mid - 1; // 相对于i来说,j 前面的数都比它小
temp[k ++] = w[i ++];
}
else {
h[w[j].y] += mid - i + 1; // 相对于j来说,i 后面的数都比它大
temp[k ++] = w[j ++];
}
}
while(i <= mid) {
h[w[i].y] += j - mid - 1;
temp[k ++] = w[i ++];
}
while(j <= r){
h[w[j].y] += mid - i + 1; // 可以不需要,因为 mid - i + 1 总为0
temp[k ++] = w[j ++];
}
for(i = l,j = 0;i <= r;i ++,j ++) w[i] = temp[j];
}
int main()
{
cin >> n;
for(int i = 0;i < n;i ++) scanf("%d",&w[i].x), w[i].y = i;
merge_sort(0,n - 1);
LL res = 0;
for(int i = 0;i < n;i ++) {
res += (1 + h[i]) * h[i] / 2;
}
cout << res << endl;
return 0;
}
树状数组
时间复杂度
$O(nlogn)$
C++ 代码
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1000010;
int w[N], tr[N];
int sum[N];
int n;
int lowbit(int x) {
return x & -x;
}
int query(int x) {
int res = 0;
for(int i = x;i; i -= lowbit(i)) res += tr[i];
return res;
}
void add(int x,int v) {
for(int i = x;i < N;i += lowbit(i)) tr[i] += v;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++) scanf("%d",&w[i]),w[i] ++;
for(int i = 1;i <= n;i ++) {
sum[i] = query(N - 1) - query(w[i]);
add(w[i],1);
}
memset(tr, 0, sizeof tr);
for(int i = n;i > 0;i --) {
sum[i] += query(w[i] - 1);
add(w[i],1);
}
LL res = 0;
for(int i = 1;i <= n;i ++) res += (LL)sum[i] * (sum[i] + 1) / 2;
cout << res << endl;
return 0;
}
归并排序中下面循环的第一行不需要,永远都是加0
感谢,已经加了注释
大佬麻烦问下,为什么必须用pair啊,感觉w[i].y=i我直接用i代替可以吗
直接存scanf(“%d”,&w[i]),这样为什么不能过啊
大佬还缺爸爸吗,我可以
这就是大佬吗,爱了爱了