[2014年第五届蓝桥杯C/C++B组省赛题目解析]---C/C++ 小朋友排队 [树状数组]

这篇博客介绍了一种利用树状数组高效解决小朋友按身高排队问题的方法。通过计算每个小朋友的前逆序对和后逆序对,确定最小的不高兴程度之和。文章详细解释了树状数组的概念、低bit计算方法以及如何利用它进行快速求和与修改。最终,通过示例展示了如何应用这些算法来找到最小的不高兴程度总和。
摘要由CSDN通过智能技术生成

题目:十、小朋友排队

n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。

如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

【数据格式】

输入的第一行包含一个整数n,表示小朋友的个数。
第二行包含 n 个整数 H1 H2 … Hn,分别表示每个小朋友的身高。
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

例如,输入:
3
3 2 1
程序应该输出:
9

【样例说明】
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

【数据规模与约定】
对于10%的数据, 1<=n<=10;
对于30%的数据, 1<=n<=1000;
对于50%的数据, 1<=n<=10000;
对于100%的数据,1<=n<=100000,0<=Hi<=1000000。

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型。

代码用到树状数组:

#include<stdio.h>
#include<string.h>
#define SIZE 1000000+10
#define N 100000+10
 
int allHigh[SIZE];     //记录每个身高的人数  题目要求最大为1000000 这里为了防止溢出加多了10
int C[SIZE];   //上面的链接讲树状数组 很清晰,c[n]=a(n-a^k+1)+.........+an (其中 k 为 n 的二进制表示中从右往左数的0 的个数)
long long b[N]; //记录每个人的前逆序对 +  后逆序对
int n;
long long judge[N];    //可以发现不高兴程度a(n) - a(n-1) ,a(n-1) - a(n-2),... ,a(1) - a(0)是是一个等差数列,这里把它们的结果记录在judge这个数组里
 
int lowbit(int x){     //求解 a^k
    return x & (-x);
}
 
void add (int i, int x) {      //当数组中的元素有变更时(这里是增加),树状数组会高效的计算
     while(i <= SIZE){
          C[i] += x;
          i += lowbit(i);
     }
}
 
int sum (int end){  // 求数组的和
    int re = 0;
    while(end > 0) {
        re += C[end];
        end -= lowbit(end);
    }
    return re;
}
 
void fun () {  //解决问题
    int i;
    for(i = 0; i < n; i ++)
    {       //遍历所以小朋友
        scanf("%d",&allHigh[i]);
        add(allHigh[i] + 1, 1);// allHigh 里 对刚获取的值小朋友的身高 的人数加一
        b[i] = (i+1) - sum(allHigh[i]+1);     //统计该小朋友b[i]的前逆序对
    }
    memset(C, 0, sizeof(C));       //接下来要计算各个人的后逆序对需要吧C[]清空
    
    for(i = n - 1; i >= 0; i --) { //遍历所以小朋友
        add(allHigh[i] + 1, 1);
        b[i] += sum(allHigh[i]);      //统计该小朋友b[i]的前逆序对 + 后逆序对
    }
 
    long long ans = 0;
    for(i = 0; i < n; i ++){   //累加不高兴值
        ans += judge[b[i]];  //调用之前已经计算好的等差数列数组
    }
    printf("%lld", ans);
}
 
int main () {
    int i;
    scanf("%d", &n);
    memset(allHigh, 0, sizeof(allHigh));  //初始化
    memset(C, 0, sizeof(C));
    memset(b, 0, sizeof(b));
    
    judge[0] = 0;
    for(i = 0; i < N; i ++){
        //a(n) - a(n-1) ,a(n-1) - a(n-2),... ,a(1) - a(0)是等差数列
        judge[i] = judge[i - 1] + i;
    }
    fun ();
    return 0;
}

转自:https://blog.csdn.net/qq_34845121/article/details/64611382

[树状数组]如下:

如果给定一个数组,要你求里面所有数的和,一般都会想到累加。但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组中的元素后,仍然要你求数组中某段元素的和,就显得麻烦了。所以我们就要用到树状数组,他的时间复杂度为O(lgn),相比之下就快得多。下面就讲一下什么是树状数组:

​ 一般讲到树状数组都会少不了下面这个图:

img

​ 下面来分析一下上面那个图看能得出什么规律:

​ 据图可知:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11…c16=a1+a2+a3+a4+a5+…+a16。

​ 分析上面的几组式子可知,当 i 为奇数时,ci=ai ;当 i 为偶数时,就要看 i 的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 c6=a5+a6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 c4=a1+a2+a3+a4(由四向前数四个数的和)。

​ (一)有公式:cn=a(n-a^k+1)+…+an(其中 k 为 n 的二进制表示中从右往左数的 0 的个数)。

​ 那么,如何求 a^k 呢?求法如下:

int lowbit(int x)
{
     return x&(-x);    
}

​ lowbit()的返回值就是 2^k 次方的值。

​ 求出来 2^k 之后,数组 c 的值就都出来了,接下来我们要求数组中所有元素的和。

​ (二)求数组的和的算法如下:

​ (1)首先,令sum=0,转向第二步;

​ (2)接下来判断,如果 n>0 的话,就令sum=sum+cn转向第三步,否则的话,终止算法,返回 sum 的值;

​ (3)n=n - lowbit(n)(将n的二进制表示的最后一个零删掉),回第二步。

​ 代码实现:

int Sum(int n)
{
    int sum=0;
    while(n>0)
    {
         sum+=c[n];
         n=n-lowbit(n);
    }    
    return sum;
}

​ (三)当数组中的元素有变更时,树状数组就发挥它的优势了,算法如下(修改为给某个节点 i 加上 x ):

​ (1)当 i<=n 时,执行下一步;否则的话,算法结束;

​ (2)ci=ci+x ,i=i+lowbit(i)(在 i 的二进制表示的最后加零),返回第一步。

​ 代码实现:

void change(int i,int x)
{
     while(i<=n)
     {
          c[i]=c[i]+x;
          i=i+lowbit(i);
     }
}

转自:http://www.cnblogs.com/zhangshu/archive/2011/08/16/2141396.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值