问题描述
在 "无影之谜" 解密大赛中,有 N 位选手,每位选手有一个影响力值 S[i]。选手 j 会投票给选手 i,当且仅当选手 j 的影响力值大于或等于他们之间(不包括选手 i 和 j)其他选手的影响力值的总和。
输入格式
- 第一行包含一个整数
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,使得从j到i-1之间的总影响力不超过S[i]。我们通过二分查找确定这个范围。 - 右侧范围: 同样,我们需要找到选手
i右边的选手j,使得从i+1到j-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. 整体流程
- 读入数据并初始化前缀和和差分数组。
- 对每个选手,使用二分查找计算其能影响的区间并更新差分数组。
- 使用前缀和计算出每个选手的最终投票数并输出结果。
时间复杂度分析
- 前缀和的计算:
O(N),直接遍历一次数组。 - 二分查找: 对于每个选手,我们分别进行两次二分查找,每次查找的时间复杂度为
O(log N)。所以,总的时间复杂度是O(N log N)。 - 差分数组更新和最终求和: 通过差分数组,我们在
O(N)时间内完成投票数的更新和计算。
所以,整体时间复杂度为 O(N log N),适合在 N 较大的情况下使用。
总结
这段代码巧妙地结合了 前缀和 和 差分数组 技术,并使用 二分查找 来优化区间和的计算。这样既能保证算法的正确性,又能够高效地解决大规模数据的处理问题。