小苯的美丽区间

[牛客链接](https://www.nowcoder.com/practice/b80c412275d44f2c8312cfedaa630faa)

题意

一个正整数 是「美丽数」当且仅当它是 的幂次。美丽值定义为 (即不断除以 的次数),非美丽数的美丽值为

给定长度为 的正整数数组 ,求所有连续子数组之和的美丽值之和。

思路

枚举 的幂次 + 前缀和哈希

暴力枚举所有子数组是 ,但注意到一个子数组和 只有在 时才有贡献 ,而 的取值只有 种( 为数组总和),这就为我们打开了突破口。

核心思路:把"枚举子数组"转化为"枚举目标值"

设前缀和 。子数组 的和为 。对于固定的 ,我们要统计满足 的有序对 )的个数,然后乘以 计入答案。

这就是经典的「两数之差为定值」问题——从左到右扫描 ,用哈希表记录之前出现过的前缀和及其次数,每次查找 是否在表中即可。

时间分析 最多到 ,每轮 ,总计 。由于 ,完全够用。

以样例 验证:前缀和为

  • :差为 的对有 ,共 对,贡献
  • :差为 的对有 ,共 对,贡献
  • :差为 的对有 ,共 对,贡献

总计 ,与预期一致。

代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        vector<long long> a(n);
        long long total = 0;
        for (int i = 0; i < n; i++) {
            scanf("%lld", &a[i]);
            total += a[i];
        }

        long long ans = 0;
        for (int k = 0; (1LL << k) <= total; k++) {
            long long pw = 1LL << k;
            unordered_map<long long, int> cnt;
            cnt.reserve(n + 1);
            long long prefix = 0;
            cnt[0] = 1;
            for (int i = 0; i < n; i++) {
                prefix += a[i];
                auto it = cnt.find(prefix - pw);
                if (it != cnt.end()) {
                    ans += (long long)k * it->second;
                }
                cnt[prefix]++;
            }
        }

        printf("%lld\n", ans);
    }
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine().trim());
        StringBuilder sb = new StringBuilder();
        while (t-- > 0) {
            int n = Integer.parseInt(br.readLine().trim());
            StringTokenizer st = new StringTokenizer(br.readLine().trim());
            long[] a = new long[n];
            long total = 0;
            for (int i = 0; i < n; i++) {
                a[i] = Long.parseLong(st.nextToken());
                total += a[i];
            }

            long ans = 0;
            for (int k = 0; (1L << k) <= total; k++) {
                long pw = 1L << k;
                HashMap<Long, Integer> cnt = new HashMap<>();
                cnt.put(0L, 1);
                long prefix = 0;
                for (int i = 0; i < n; i++) {
                    prefix += a[i];
                    Integer c = cnt.get(prefix - pw);
                    if (c != null) {
                        ans += (long) k * c;
                    }
                    cnt.merge(prefix, 1, Integer::sum);
                }
            }

            sb.append(ans).append('\n');
        }
        System.out.print(sb);
    }
}

复杂度分析

  • 时间复杂度,其中 为数组总和。外层枚举 个幂次,内层每次 哈希扫描。
  • 空间复杂度,哈希表存储前缀和。