#P2709. 第3题-小苯的美丽数
-
1000ms
Tried: 34
Accepted: 14
Difficulty: 5
所属公司 :
阿里
时间 :2025年3月16日-阿里云(开发岗)
-
算法标签>前缀和
第3题-小苯的美丽数
题解
题面描述
给定一个长度为 n 的正整数数组 a。我们定义一个数 x 的 美丽值 如下:
- 如果将 x 不停除以 2 直到无法整除,此时得到的结果恰好为 1,则称 x 是 美丽数,其美丽值为在过程中除以 2 的次数。
- 否则,x 不是美丽数,其美丽值记为 0。
题目要求计算数组 a 中所有连续子数组(即区间)的和的美丽值之和。设某个子数组的和为 S,其贡献为 f(S)(若 S=2k 则 f(S)=k,否则 f(S)=0),要求求
∑l=1n∑r=lnf(∑i=lrai)
思路分析
-
美丽值判定:
一个数 x 满足“美丽”当且仅当 x 为 2 的幂,即 x=2k。此时美丽值为 k(注意当 x=1=20 时,美丽值为 0)。 -
连续子数组求和转化:
利用前缀和数组 P,其中 P[0]=0,P[i]=∑j=1iaj。任意子数组和可以写成
S=P[r]−P[l−1].
于是问题转化为:对于每个区间 (l,r),如果 P[r]−P[l−1]=2k(且 k≥1,因为 k=0贡献为 0),则贡献为 k。 -
双重枚举优化:
枚举右端点 r,利用哈希表记录之前所有前缀和出现的次数。对当前前缀和 S=P[r],对于每个可能的指数 k(通常 k 最大约 50 足够),令
target=S−2k, 则所有满足 P[l−1]=target 的区间,其子数组和正好为 2k,贡献为 k。累加所有贡献即可。 -
时间复杂度:
每个 r 枚举最多 50 个 k,整体复杂度为 O(n⋅50),可以在 n≤3×105 内通过。
C++
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T--){
int n;
cin >> n;
vector<ll> a(n);
for (int i = 0; i < n; i++){
cin >> a[i];
}
// 哈希表统计前缀和出现次数
unordered_map<ll, ll> freq;
freq[0] = 1; // 前缀和为0出现一次
ll prefix = 0;
ll ans = 0;
for (int i = 0; i < n; i++){
prefix += a[i];
// 枚举可能的 k 值, 使得 2^k 不超过当前可能的和范围
for (int k = 1; k < 60; k++){
ll power = 1LL << k; // 计算 2^k
ll target = prefix - power;
if(freq.find(target) != freq.end()){
// 累加答案, 美丽值为 k
ans += (ll)k * freq[target];
}
}
// 更新当前前缀和的次数
freq[prefix]++;
}
cout << ans << "\n";
}
return 0;
}
Python
# Python 版本解法
import sys
def main():
import sys
input_data = sys.stdin.read().split()
t = int(input_data[0])
index = 1
res = []
for _ in range(t):
n = int(input_data[index])
index += 1
a = list(map(int, input_data[index:index+n]))
index += n
freq = {0: 1} # 用字典统计前缀和出现次数
prefix = 0
ans = 0
# 遍历数组计算前缀和
for num in a:
prefix += num
# 枚举可能的 k 值, 计算 2^k
for k in range(1, 60):
power = 1 << k # 计算 2^k
target = prefix - power
if target in freq:
ans += k * freq[target]
freq[prefix] = freq.get(prefix, 0) + 1
res.append(str(ans))
sys.stdout.write("\n".join(res))
if __name__ == '__main__':
main()
Java
import java.io.*;
import java.util.*;
// Java 版本解法
public class Main {
public static void main(String[] args) throws IOException {
// 使用 BufferedReader 和 PrintWriter 提高输入输出效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int T = Integer.parseInt(br.readLine().trim());
while(T-- > 0){
int n = Integer.parseInt(br.readLine().trim());
String[] parts = br.readLine().trim().split(" ");
long[] a = new long[n];
for(int i = 0; i < n; i++){
a[i] = Long.parseLong(parts[i]);
}
// 使用 HashMap 统计前缀和出现次数
HashMap<Long, Long> freq = new HashMap<>();
freq.put(0L, 1L); // 前缀和 0 出现一次
long prefix = 0;
long ans = 0;
// 遍历数组计算前缀和
for(int i = 0; i < n; i++){
prefix += a[i];
// 枚举可能的 k 值, 计算 2^k
for(int k = 1; k < 60; k++){
long power = 1L << k; // 计算 2^k
long target = prefix - power;
if(freq.containsKey(target)){
ans += k * freq.get(target);
}
}
freq.put(prefix, freq.getOrDefault(prefix, 0L) + 1);
}
out.println(ans);
}
out.flush();
out.close();
}
}
题目内容
小苯认为一个数x是美丽数,当且仅当:如果将x不停除以2,直到x不整除2时停止,此时x恰好等于1。
如果一个数美丽,则其美丽值为:以上操作中除以2的次数。
否则一个数不美丽,则其美丽值为0。
现在小苯有一个长度为n的数组a,他想知道a中所有连续子数组的和的美丽值之和是多少,请你帮他算一算吧。
形式化的:记数字x的美丽值为f(x)则请你求出
∑t=1n∑r=lnf(al+al+1+...+ar)
(其中al+al+1+...ar表示a数组在[l,r]这一段区间的所有元素之和)
输入描述
每个测试文件均包含多组测试数据。第一行输入一个整数T(1≤T≤104) 代表数据组数,每组测试数据描述如下:
第一行输入一个正整数n(1≤n≤3×105),表示数组a的长度。
第二行n个正整数a1a2….an(0≤ai<230),表示数组a。
(保证同一个测试文件中的测试数据里,n的总和不超过3×105)
输出描述
对于每个测试数据,在单独的一行输出一个整数表示答案。
样例1
输入
2
5
2 4 4 3 5
4
2 2 2 2
输出
15
13
说明
对于第二组测试数据:{2,2,2,2};
所有长度为1的子区间和都是美丽的,因为2的二进制中只有一个1,其美丽值为1,因此总美丽值为1×4=4;
所有长度为2的子区间和都是美丽的,因为他们的和都是4,4的美丽值为2,其总美丽值为2×3=6。
所有长度为3的子区间和都不是美丽的,因此美丽值总和为0。
唯一一个长度为4的子区间和是美丽的,其总和为8,美丽值为3
综上,所有区间的总美丽值之和为:4+6+0+3=13