题目链接
题目描述
小苯定义一个数 是美丽数,当且仅当:将
不停除以 2,直到结果
不整除 2 时停止,此时
恰好等于 1。
-
如果一个数是美丽数,其美丽值为:以上操作中除以 2 的次数。
-
否则,其美丽值为 0。
现在给定一个长度为 的数组
,请你计算所有连续子数组的和的美丽值之和。
形式化地,记数字 的美丽值为
,请你求出
。
思路分析
1. 解读“美丽数”和“美丽值”
-
美丽数:一个数
不停地除以2,直到结果为奇数。如果这个奇数是1,那么
就是美丽数。这等价于说,一个数是美丽数,当且仅当它是2的整数次幂(
)。
-
美丽值:如果一个数
是美丽数,其美丽值为
。否则,美丽值为0。这个
实际上就是
,也等于
的二进制表示中末尾0的个数。
2. 问题转换:从子数组到前缀和
我们的目标是计算所有子数组的和的美丽值。直接枚举所有子数组(个)并求和(
),总复杂度是
,无法接受。
我们可以利用前缀和来优化子数组求和。设前缀和数组 P
,其中 ,并且我们定义
。那么,子数组
(0-indexed) 的和就是
。
现在问题变为:计算所有满足 的数对
,对它们对应的和
求美丽值,并累加。
由于美丽值只对2的幂次有意义,我们只需要关心那些和恰好是 的子数组。也就是说,我们需要找到所有满足
的数对
,对于每个这样的数对,其贡献的美丽值为
。
3. 核心算法:前缀和 + 哈希表
我们可以将条件 变形为
。
这个形式启发我们使用一个高效的算法:当我们遍历数组,计算到前缀和 时,我们不再回头去找符合条件的
,而是向前看:我们需要知道在
之前,有多少个前缀和
等于
?
我们可以用一个**哈希表(Map/Dictionary)**来动态地记录到目前为止出现过的所有前缀和及其出现次数。
算法步骤:
-
初始化总美丽值
total_beauty = 0
。 -
初始化一个哈希表
counts
,并放入{0: 1}
,表示数值为0的前缀和(即)已经出现过1次。
-
初始化当前前缀和
current_sum = 0
。 -
遍历数组
的每个元素
:
a. 更新
current_sum = current_sum + a_i
。这相当于计算了。
b. 对于这个新的
current_sum
,我们检查它是否能与之前的前缀和构成一个和为2的幂的子数组。我们遍历所有可能的2的幂次(
从0开始)。
i. 计算
target = current_sum - 2^k
。ii. 如果
target
在哈希表counts
中存在,说明我们找到了counts[target]
个子数组,它们的和都是。
iii. 这些子数组的美丽值都是
。我们将
累加到
total_beauty
中。c. 将当前的
current_sum
存入哈希表:counts[current_sum]++
。 -
遍历结束后,
total_beauty
就是最终答案。
最大可能的数组和约为 。
约等于
,所以
的取值范围大约在0到50之间,这是一个很小的常数,因此内层循环是可接受的。
代码
#include <iostream>
#include <vector>
#include <map>
using namespace std;
void solve() {
int n;
cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
map<long long, int> counts;
counts[0] = 1;
long long current_sum = 0;
long long total_beauty = 0;
for (int i = 0; i < n; ++i) {
current_sum += a[i];
for (int p = 0; p < 60; ++p) {
long long power_of_2 = 1LL << p;
long long target = current_sum - power_of_2;
if (counts.count(target)) {
total_beauty += (long long)p * counts[target];
}
}
counts[current_sum]++;
}
cout << total_beauty << endl;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
solve(sc);
}
}
private static void solve(Scanner sc) {
int n = sc.nextInt();
long[] a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
}
Map<Long, Integer> counts = new HashMap<>();
counts.put(0L, 1);
long currentSum = 0;
long totalBeauty = 0;
for (int i = 0; i < n; i++) {
currentSum += a[i];
for (int p = 0; p < 60; p++) {
long powerOf2 = 1L << p;
long target = currentSum - powerOf2;
if (counts.containsKey(target)) {
totalBeauty += (long) p * counts.get(target);
}
}
counts.put(currentSum, counts.getOrDefault(currentSum, 0) + 1);
}
System.out.println(totalBeauty);
}
}
import sys
def solve():
n = int(sys.stdin.readline())
a = list(map(int, sys.stdin.readline().split()))
counts = {0: 1}
current_sum = 0
total_beauty = 0
for x in a:
current_sum += x
for p in range(60): # 2^60 is large enough for constraints
power_of_2 = 1 << p
if power_of_2 > current_sum:
break
target = current_sum - power_of_2
if target in counts:
total_beauty += p * counts[target]
counts[current_sum] = counts.get(current_sum, 0) + 1
print(total_beauty)
def main():
try:
t_str = sys.stdin.readline()
if not t_str: return
t = int(t_str)
for _ in range(t):
solve()
except (IOError, ValueError):
return
main()
算法及复杂度
-
算法:前缀和 + 哈希表
-
时间复杂度:
,其中
是数组长度,
是数组可能的最大前缀和。由于
可以很大(约
),
大约是 50-60,可以看作一个常数。因此,总时间复杂度可以认为是
。
-
空间复杂度:
,在最坏情况下,所有前缀和都不同,哈希表需要存储
个键值对。