蓝桥杯 无影之谜

原题目链接

问题描述

在 "无影之谜" 解密大赛中,有 N 位选手,每位选手有一个影响力值 S[i]。选手 j 会投票给选手 i,当且仅当选手 j 的影响力值大于或等于他们之间(不包括选手 ij)其他选手的影响力值的总和。

输入格式

  • 第一行包含一个整数 N,表示选手的数量。
  • 第二行包含 N 个空格分隔的整数 S[1], S[2], ..., S[N],表示每个选手的影响力值。

输出格式

  • 输出一行包含 N 个空格分隔的整数,表示每个选手得到的投票数。

样例输入

in 复制代码
4
4 3 2 1

样例输出

out 复制代码
1 2 3 2

这段代码实现了一个优化的算法,用来解决题目中选手投票问题。我们来逐步分析其思路,并与解题过程对照。

c++代码

cpp 复制代码
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

ll N, s, e, temp;
ll S[100006], prefix_sum[100006], answer[100006];

// 快速求区间和(返回值为ll,避免溢出)
ll get_interval_sum(int i, int j) {
    if (i <= 0 || j <= 0 || i > N || j > N || i > j) return 0;
    return prefix_sum[j] - prefix_sum[i - 1];
}

// 快速区间加一(差分思想)
void quick_add_one(int i, int j) {
    if (i <= 0 || j <= 0 || i > N || j > N || i > j) return;
    answer[i]++;
    if (j + 1 <= N) answer[j + 1]--;
}

int main() {
    cin >> N;
    fill(prefix_sum + 1, prefix_sum + N + 1, 0);
    fill(answer + 1, answer + N + 1, 0);

    for (int i = 1; i <= N; i++) {
        cin >> S[i];
        prefix_sum[i] = prefix_sum[i - 1] + S[i];
    }

    for (int i = 1; i <= N; i++) {
        s = 1, e = i - 1, temp = -1;
        while (s <= e) {
            int mid = (s + e) / 2;
            if (get_interval_sum(mid + 1, i - 1) <= S[i]) {
                temp = mid;
                e = mid - 1;
            }
            else s = mid + 1;
        }
        if (temp != -1) quick_add_one(temp, i - 1);

        s = i + 1, e = N, temp = -1;
        while (s <= e) {
            int mid = (s + e) / 2;
            if (get_interval_sum(i + 1, mid - 1) <= S[i]) {
                temp = mid;
                s = mid + 1;
            }
            else e = mid - 1;
        }
        if (temp != -1) quick_add_one(i + 1, temp);
    }

    // 计算差分前缀和,得到最终结果
    for (int i = 1; i <= N; i++) {
        answer[i] += answer[i - 1];
        cout << answer[i] << (i == N ? "\n" : " ");
    }
    return 0;
}//by wqs

思路和步骤分析

1. 前缀和计算

在这个算法中,首先我们使用前缀和数组来高效计算选手之间的影响力值总和。这样对于任意区间 [i, j],我们都可以在 O(1) 的时间内通过 prefix_sum[j] - prefix_sum[i-1] 来获得该区间的影响力值和。

cpp 复制代码
ll get_interval_sum(int i, int j) {
    if (i <= 0 || j <= 0 || i > N || j > N || i > j) return 0;
    return prefix_sum[j] - prefix_sum[i - 1];
}

2. 快速区间更新(差分思想)

为了高效地记录每个选手的投票数,我们使用了差分数组 answer[] 来进行快速更新。对于每一个选手,假设我们找到了满足投票条件的区间,我们可以将该区间的起始位置 i 处加 1,并且在该区间的结束位置 j+1 处减 1。然后,通过累加差分数组,最终得到每个选手的投票数。

cpp 复制代码
void quick_add_one(int i, int j) {
    if (i <= 0 || j <= 0 || i > N || j > N || i > j) return;
    answer[i]++;
    if (j + 1 <= N) answer[j + 1]--;
}

3. 二分查找优化

接下来,我们使用二分查找来寻找每个选手 i 在左侧和右侧可以影响的范围。我们要找的条件是,选手 j 会投票给选手 i,当且仅当 S[j] >= sum(S[i+1]...S[j-1])。这里的 sum(S[i+1]...S[j-1]) 就是我们通过前缀和数组求得的区间和。

  • 左侧范围: 我们需要找到选手 i 左边的选手 j,使得从 ji-1 之间的总影响力不超过 S[i]。我们通过二分查找确定这个范围。
  • 右侧范围: 同样,我们需要找到选手 i 右边的选手 j,使得从 i+1j-1 之间的总影响力不超过 S[i]。我们也通过二分查找确定这个范围。
cpp 复制代码
s = 1, e = i - 1, temp = -1;
while (s <= e) {
    int mid = (s + e) / 2;
    if (get_interval_sum(mid + 1, i - 1) <= S[i]) {
        temp = mid;
        e = mid - 1;
    }
    else s = mid + 1;
}
if (temp != -1) quick_add_one(temp, i - 1);

s = i + 1, e = N, temp = -1;
while (s <= e) {
    int mid = (s + e) / 2;
    if (get_interval_sum(i + 1, mid - 1) <= S[i]) {
        temp = mid;
        s = mid + 1;
    }
    else e = mid - 1;
}
if (temp != -1) quick_add_one(i + 1, temp);

4. 最终结果计算

通过上述步骤,所有满足条件的投票已经通过差分数组 answer[] 记录下来。最后,我们只需计算前缀和,得到每个选手的投票数。

cpp 复制代码
for (int i = 1; i <= N; i++) {
    answer[i] += answer[i - 1];
    cout << answer[i] << (i == N ? "\n" : " ");
}

5. 整体流程

  1. 读入数据并初始化前缀和和差分数组。
  2. 对每个选手,使用二分查找计算其能影响的区间并更新差分数组。
  3. 使用前缀和计算出每个选手的最终投票数并输出结果。

时间复杂度分析

  • 前缀和的计算: O(N),直接遍历一次数组。
  • 二分查找: 对于每个选手,我们分别进行两次二分查找,每次查找的时间复杂度为 O(log N)。所以,总的时间复杂度是 O(N log N)
  • 差分数组更新和最终求和: 通过差分数组,我们在 O(N) 时间内完成投票数的更新和计算。

所以,整体时间复杂度为 O(N log N),适合在 N 较大的情况下使用。

总结

这段代码巧妙地结合了 前缀和差分数组 技术,并使用 二分查找 来优化区间和的计算。这样既能保证算法的正确性,又能够高效地解决大规模数据的处理问题。

相关推荐
武帝为此2 小时前
【专家系统介绍】
人工智能·算法
@insist1232 小时前
软件设计师-分治法核心原理与典型应用
算法·软考·软件设计师·软件水平考试
机器学习之心2 小时前
PSO-SVR粒子群算法优化支持向量机回归+SHAP分析+新数据预测,MATLAB代码
算法·支持向量机·回归·pso-svr·灰狼算法优化支持向量机回归
8Qi82 小时前
环形链表刷题笔记(LeetCode热题100--141、142)
c语言·数据结构·c++·算法·leetcode·链表
滴滴答滴答答2 小时前
机考刷题之 13 LeetCode 1004 最大连续1的个数 III
java·算法·leetcode
一叶落4382 小时前
139. 单词拆分(Word Break)
c语言·数据结构·算法·leetcode·深度优先·图论
Yupureki2 小时前
《算法竞赛从入门到国奖》算法基础:数据结构-单调队列
c语言·数据结构·c++·算法
逆境不可逃2 小时前
【从零入门23种设计模式17】行为型之中介者模式
java·leetcode·microsoft·设计模式·职场和发展·中介者模式
重生之我是Java开发战士2 小时前
【递归、搜索与回溯】穷举,暴搜,深搜,回溯,剪枝:全排列与子集
算法·机器学习·剪枝