题目:十、小朋友排队
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),相比之下就快得多。下面就讲一下什么是树状数组:
一般讲到树状数组都会少不了下面这个图:
下面来分析一下上面那个图看能得出什么规律:
据图可知: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