解题思路
这道题的关键点在于:
个点形成一条链,边的编号从
到
- 对于第
条边,其权值
定义为:在第
条边左侧
且点权为
的点数,与在第
条边右侧
且点权为
的点数的乘积之和
- 需要计算每条边的权值
解题思路:
- 使用两个哈希表分别记录左侧和右侧的点权计数
- 初始时所有点都在右侧
- 从左到右遍历每个位置,计算当前边的权值:
- 对于每个不同的点权值,贡献为左侧该值的个数 × 右侧该值的个数
- 移动当前点到左侧,更新计数
代码
#include <iostream>
#include <unordered_map>
using namespace std;
int nums[100005];
void solve(int n) {
long long pre = 0;
unordered_map<int, int> left, right;
// 初始时所有点都在右侧
for (int i = n - 1; i >= 0; --i)
right[nums[i]]++;
// 计算每条边的权值
for (int i = 0; i < n - 1; ++i) {
// 计算当前边的权值并输出
printf("%lld%c", pre += n - 2 * i - right[nums[i]] + left[nums[i]],
i == n - 2 ? '\n' : ' ');
// 将当前点从右侧移到左侧
right[nums[i]]--;
left[nums[i]]++;
}
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
scanf("%d", &nums[i]);
solve(n);
return 0;
}
import java.util.*;
public class Main {
public static void solve(int n, int[] nums) {
long pre = 0;
Map<Integer, Integer> left = new HashMap<>();
Map<Integer, Integer> right = new HashMap<>();
// 初始时所有点都在右侧
for (int i = n - 1; i >= 0; i--) {
right.put(nums[i], right.getOrDefault(nums[i], 0) + 1);
}
// 计算每条边的权值
for (int i = 0; i < n - 1; i++) {
pre += n - 2 * i - right.getOrDefault(nums[i], 0) +
left.getOrDefault(nums[i], 0);
System.out.print(pre + (i == n - 2 ? "\n" : " "));
// 将当前点从右侧移到左侧
right.put(nums[i], right.get(nums[i]) - 1);
left.put(nums[i], left.getOrDefault(nums[i], 0) + 1);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
solve(n, nums);
}
}
def solve(n, nums):
pre = 0
left = {}
right = {}
# 初始时所有点都在右侧
for x in nums:
right[x] = right.get(x, 0) + 1
# 计算每条边的权值
for i in range(n-1):
pre += n - 2 * i - right.get(nums[i], 0) + left.get(nums[i], 0)
print(pre, end='\n' if i == n-2 else ' ')
# 将当前点从右侧移到左侧
right[nums[i]] -= 1
left[nums[i]] = left.get(nums[i], 0) + 1
n = int(input())
nums = list(map(int, input().split()))
solve(n, nums)
算法及复杂度
- 算法:哈希表 + 动态维护
- 时间复杂度:
- 只需要遍历一次数组
- 空间复杂度:
- 需要两个哈希表存储点权计数