1049 数列的片段和 (20 分)
题意描述:
给定一个正数数列,我们可以从中截取任意的连续的几个数,称为片段。例如,给定数列 { 0.1, 0.2, 0.3, 0.4 },我们有 (0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3) (0.2, 0.3, 0.4) (0.3) (0.3, 0.4) (0.4) 这 10 个片段。
给定正整数数列,求出全部片段包含的所有的数之和。如本例中 10 个片段总和是 0.1 + 0.3 + 0.6 + 1.0 + 0.2 + 0.5 + 0.9 + 0.3 + 0.7 + 0.4 = 5.0。
输入格式:
输入第一行给出一个不超过 10^5 的正整数 N,表示数列中数的个数,第二行给出 N 个不超过 1.0 的正数,是数列中的数,其间以空格分隔。
输出格式:
在一行中输出该序列所有片段包含的数之和,精确到小数点后 2 位。
输入样例:
4
0.1 0.2 0.3 0.4
输出样例:
5.00
解题思路:
Alice: 这道题,,,看起来真有劲啊。虽然没看出来该怎么做,直接求所有的集合吗,感觉不太好算啊。
Bob: 直接求所有集合看起来就是求一个集合的所有子集了,和问题里描述的不太一样,问题描述里是有顺序的,不会这么麻烦吧。
Alice: 找规律,找规律,看那个和式好像有规律哦,你看:
把和式根据第一个元素的不同分成四段,每段中的相邻两项的差就是数组里面对应的项。你看{0.1 , 0.2, 0.3, 0.4} 这个,在和式里面第一个是0.1 ,第二个就是 0.1 + 0.2, 然后第三个就是 0.1 + 0.2 +0.3 ,然后第四个就是0.1+ 0.2 + 0.3+ 0.4。第二个集合{0.2,0.3,0.4}也是这样,重复往后加就行了。O(∩_∩)O哈哈~
Bob: emmm, (σ゚∀゚)σ…:*☆ 哎哟不错哦。写代码试试吧。
Alice:啊Σ(っ°Д°;)っ,有两个测试点超时了:emmm,你用C++ 再写一遍试试,看看是不是Python太慢的原因。
- Alice’s O(N^2) Python Version:
def main():
N = int(input())
data = [float(x) for x in input().split()]
answer = 0
for x in range(len(data)):
atom = data[x]
answer += atom
for y in range(x + 1, len(data)):
atom += data[y]
answer += atom
print("{:.2f}".format(answer))
if __name__ == '__main__':
main()
Bob: 看起来不是啊,我这儿也是后两个测试点超时了:┐( ̄ヮ ̄)┌
- Bob’s O(N^2) C++ Version:
#include <stdio.h>
#define NN 100000 + 10
int main(){
int N = 0;
float data[NN];
scanf("%d", &N);
for(int i=0; i<N; ++i){
scanf("%f", &data[i]);
}
float answer = 0, atom = 0;
for(int i=0; i<N; ++i){
atom = data[i];
answer += atom;
for(int j=i+1; j<N; ++j){
atom += data[j];
answer += atom;
}
}
printf("%.2f\n", answer);
return 0;
}
Alice: 真是让人头大,看来O(N^2) 的方法不行啊,那是有O(N)的算法??
Bob: O(N)的算法就是只有一层循环,只能遍历一次数据,如果能知道每个数据最后在和式中出现的次数就好了,就能直接求和了。
Alice: 每个元素在最终的和式中出现的次数》》》》
Bob:(o゜▽゜)o☆有了!
看这个图,每个元素下面的红线穿越横线的次数就是每个元素最终在和式中出现的次数。我直接说啦,出现的次数可以分为两部分,一部分是 “搭了前面元素的顺风车”, 一部分是从自身出发。比如说0.2, 从0.1开始的带有0.2的有3个,从0.2开始的带有0.2的有三个,
Alice: 等等等等, 从0.1开始的带有0.2的有三个,从0.2开始的带有0.2的也有三个,这三个好像就是从0.2开始截止到列表末尾所有元素的个数啊。而2个 三 就是因为0.2是第二个元素啊。那么0.1的个数应该是从0.1开始截止到末尾的元素个数乘以0.1的起始位置,就是4 * 1 == 4,0.3的个数应该是 2 * 3 == 6。
Bob: 就是这样的,0.3出现的地方可以分为3部分,而且在每个地方都是出现了两次,就像上面图上画的那样。
Alice: (~ ̄▽ ̄)~ 总结一下,每个元素在最终和式中出现的次数等于 (该元素的下标 + 1)* (该元素到列表末尾的元素个数)
Bob: o(*≧▽≦)ツ
代码:
- Alice’s Smart Python Version :
def main():
N = int(input())
# 接收输入的整数
data = [float(x) for x in input().split()]
# 接收输入的浮点数,Python中只有float 这一类型而没有 double
answer = 0
# 求和记录
for x in range(len(data)):
# 依次遍历所有的输入数据
answer += (len(data) - x) * (x + 1) * data[x]
# (len(data) - x) * (x + 1) 为每个元素在最后的和式之中出现的次数
print("{:.2f}".format(answer))
# 保留两位小数输出答案
if __name__ == '__main__':
main()
- Bob’s funny C++ Version:
#include <stdio.h>
int main(){
int N = 0;
double temp, answer = 0;
// 由于只需要遍历一遍输入的数据,我们可以在输入的同时完成计算的操作,因此不需要定义一
// 个数组来接收输入的数据。
scanf("%d", &N);
for(int i=0; i<N; ++i){
scanf("%lf", &temp);
answer += temp * (N-i) * (i+1);
// 接收输入的数据 并 计算
}
printf("%.2lf\n", answer);
return 0;
}
易错点:
O(N^2)
的操作会超时,请耐心寻找O(N)
的公式。- 如果使用
C/C++
,用double
来存储输入和输出,亲测float
精度不够。
总结:
C++ consume | Python consume |
---|---|
![]() | ![]() |
For relax :