树状数组-蓝桥杯省赛C++B组-小朋友排队

树状数组-蓝桥杯省赛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]。 nh[i]ih[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次。 30+2=221+1=212+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}。 n105W[i]5×109intW[i]llll(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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值