树状数组-蓝桥杯省赛C++B组-小朋友排队
题目:
n 个小朋友站成一排。
现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。
每个小朋友都有一个不高兴的程度。
开始的时候,所有小朋友的不高兴程度都是 0。
如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k。
请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。
如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。
输入格式
输入的第一行包含一个整数 n,表示小朋友的个数。
第二行包含 n 个整数 H1,H2,…,Hn,分别表示每个小朋友的身高。
输出格式
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
数据范围
1≤n≤100000,
0≤Hi≤1000000
输入样例:
3
3 2 1
输出样例:
9
样例解释
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。
题解:
过 程 类 似 于 冒 泡 排 序 , 对 于 n 个 无 序 数 , 通 过 相 邻 两 项 交 换 得 到 有 序 序 列 , 每 一 个 数 h [ i ] 需 要 交 换 的 次 数 取 决 于 前 i 个 数 当 中 比 h [ i ] 大 的 数 的 个 数 与 区 间 [ i + 1 , n ] 的 数 中 比 h [ i ] 小 的 数 的 个 数 之 和 , 记 为 s u m [ i ] 。 过程类似于冒泡排序,对于n个无序数,通过相邻两项交换得到有序序列,每一个数h[i]需要交换的次数取决于\\前i个数当中比h[i]大的数的个数与区间[i+1,n]的数中比h[i]小的数的个数之和,记为sum[i]。 过程类似于冒泡排序,对于n个无序数,通过相邻两项交换得到有序序列,每一个数h[i]需要交换的次数取决于前i个数当中比h[i]大的数的个数与区间[i+1,n]的数中比h[i]小的数的个数之和,记为sum[i]。
如 样 例 , 3 需 要 交 换 的 次 数 为 0 + 2 = 2 次 , 2 需 要 交 换 的 次 数 为 1 + 1 = 2 次 , 1 需 要 交 换 的 次 数 为 2 + 0 = 2 次 。 如样例,3需要交换的次数为0+2=2次,2需要交换的次数为1+1=2次,1需要交换的次数为2+0=2次。 如样例,3需要交换的次数为0+2=2次,2需要交换的次数为1+1=2次,1需要交换的次数为2+0=2次。
采 用 树 状 数 组 求 s u m [ i ] 的 过 程 : 采用树状数组求sum[i]的过程: 采用树状数组求sum[i]的过程:
① 、 从 前 往 后 遍 历 , 求 和 加 上 高 度 大 于 h [ i ] 的 数 的 总 个 数 , 接 着 将 h [ i ] 加 入 到 树 状 数 组 中 去 。 ①、从前往后遍历,求和加上高度大于h[i]的数的总个数,接着将h[i]加入到树状数组中去。 ①、从前往后遍历,求和加上高度大于h[i]的数的总个数,接着将h[i]加入到树状数组中去。
② 、 从 后 往 前 遍 历 , 求 和 加 上 高 度 小 于 h [ i ] 的 数 的 总 个 数 , 接 着 将 h [ i ] 加 入 到 树 状 数 组 中 去 。 ②、从后往前遍历,求和加上高度小于h[i]的数的总个数,接着将h[i]加入到树状数组中去。 ②、从后往前遍历,求和加上高度小于h[i]的数的总个数,接着将h[i]加入到树状数组中去。
注 意 , 在 ② 开 始 之 前 , 树 状 数 组 需 要 先 清 空 ① 操 作 的 数 据 。 注意,在②开始之前,树状数组需要先清空①操作的数据。 注意,在②开始之前,树状数组需要先清空①操作的数据。
那 么 对 第 i 个 数 , 其 “ 不 高 兴 程 度 " W [ i ] = ∑ j = 1 s u m [ i ] j = ( s u m [ i ] + 1 ) × s u m [ i ] 2 。 那么对第i个数,其“不高兴程度"W[i]=\sum_{j=1}^{sum[i]}j=\frac{(sum[i]+1)×sum[i]}{2}。 那么对第i个数,其“不高兴程度"W[i]=∑j=1sum[i]j=2(sum[i]+1)×sum[i]。
最 终 结 果 就 是 对 W [ i ] 求 和 。 最终结果就是对W[i]求和。 最终结果就是对W[i]求和。
注 意 : n 的 范 围 到 1 0 5 , W [ i ] 的 极 端 情 况 可 能 达 到 约 5 × 1 0 9 爆 了 i n t , 最 终 对 W [ i ] 求 和 的 结 果 用 l l 来 存 。 并 且 , 要 先 转 化 l l 再 相 乘 , 否 则 在 计 算 过 程 中 会 爆 , 如 ( l l ) ( ( s u m [ i ] + 1 ) × s u m [ i ] 2 ) 是 先 计 算 再 转 化 , i n t 是 会 溢 出 的 。 正 确 写 法 是 ( l l ) ( s u m [ i ] + 1 ) × s u m [ i ] 2 。 注意:n的范围到10^5,W[i]的极端情况可能达到约5×10^9爆了int,最终对W[i]求和的结果用ll来存。\\并且,要先转化ll再相乘,否则在计算过程中会爆,如(ll)(\frac{(sum[i]+1)×sum[i]}{2})是先计算再转化,int是会溢出的。\\正确写法是(ll)\frac{(sum[i]+1)×sum[i]}{2}。 注意:n的范围到105,W[i]的极端情况可能达到约5×109爆了int,最终对W[i]求和的结果用ll来存。并且,要先转化ll再相乘,否则在计算过程中会爆,如(ll)(2(sum[i]+1)×sum[i])是先计算再转化,int是会溢出的。正确写法是(ll)2(sum[i]+1)×sum[i]。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x7fffffff
using namespace std;
const int N=1e6+10;
int n,h[N],tr[N];
int sum[N];///sum[i]:前i个中高度大于h[i]和i后面高度小于h[i]的数量之和
int lowbit(int x)
{
return x&(-x);
}
void add(int p,int w)
{
for(int i=p;i<=N;i+=lowbit(i)) tr[i]+=w;
}
int query(int x)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i)) res+=tr[i];
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&h[i]),h[i]++;///下标从1开始
///从前往后遍历,求每个数前面有多少个数比它大
for(int i=1;i<=n;i++)
{
sum[i]+=query(N)-query(h[i]);
add(h[i],1);///当前这个数算进去
}
///从后往前遍历,求每个数后面有多少个数比它小
memset(tr,0,sizeof(tr));///置0!!!不能忘
for(int i=n;i>0;i--)
{
sum[i]+=query(h[i]-1);
add(h[i],1);
}
ll ans=0;
for(int i=1;i<=n;i++) ans+=(ll)sum[i]*(sum[i]+1)/2;
cout<<ans<<endl;
return 0;
}