基于哈希表与差分前缀和解决撒狗粮问题
一 题解
#include<bits/stdc++.h>
using namespace std;
bool is_single(string s , const unordered_map<string , string>& couple_map , const unordered_map<string , int>& pos){auto it_couple = couple_map.find(s);//判断没有情侣的情况if(it_couple == couple_map.end()) return true;string ss = it_couple -> second;auto it_pos = pos.find(ss);//判断情侣不在的情况return it_pos == pos.end();
}
int main(){int n;unordered_map<string , string> couple_map;vector<pair<string, string>> couples;cin >> n;for(int i = 0 ; i < n ; i++){string a , b;cin >> a >> b;couple_map[a] = b;couple_map[b] = a;couples.emplace_back(a , b);}//遍历座位int m;cin >> m;vector<string> seat;unordered_map<string , int> pos;for(int i = 0 ; i < m ; i++){string s;cin >> s;seat.push_back(s);pos[s] = i;}//处理是否是单身狗vector<int> single_arr(m , 0);for(int i = 0 ; i < m ; i++){if(is_single(seat[i] , couple_map , pos)){single_arr[i] = 1;}}//差分记录撒狗粮贡献vector<int> diff(m + 2 , 0);for(auto& p : couples){string s1 = p.first;string s2 = p.second;//检查情侣是否在场auto it1 = pos.find(s1);auto it2 = pos.find(s2);if(it1 == pos.end() || it2 == pos.end()) continue;int i = it1 -> second , j = it2 -> second;if(i > j) swap(i , j);if(j - i == 1){//情侣紧挨着int left = i - 1;if(left >= 0){diff[left]++;diff[left+1]--;}int right = j + 1;if(right < m){diff[right]++;diff[right+1]--;}}else{int a = i + 1 , b = j - 1;diff[a]++;diff[b + 1]--;}}//统计被撒狗粮单身狗unordered_map<string , int> count_map;int cur = 0;for(int i = 0 ; i < m ; i++){cur += diff[i];if(single_arr[i]){count_map[seat[i]] = cur;}}//找到最大被撒次数int max_count = 0;for(auto& p : count_map){if(p.second > max_count){max_count = p.second;}}//收集结果vector<string>result;for(auto& p : count_map){if(p.second == max_count){result.push_back(p.first);}}//排序结果sort(result.begin(), result.end());// 9. 输出结果(首尾无多余空格)for (int i = 0; i < result.size(); ++i) {if (i > 0) cout << " ";cout << result[i];}cout << endl;return 0;
}
二、题目的背景需求
- 聚会上情侣坐次不同,对单身狗 “撒狗粮” 的范围不同:
- 情侣相邻:两侧单身狗各被撒 1 次(单点更新);
- 情侣不相邻:中间所有单身狗各被撒 1 次(区间更新)。
- 需求:统计被撒狗粮次数最多的单身狗,按 ID 升序输出。
- 数据规模:情侣对数 N≤5e4,总人数 M≤8e4
三、程序中运用到的差分与前缀和
1 前缀和
前缀和就是前面i个数之和,是一种预处理操作,它通过计算数组的前缀和数组,使得我们可以快速查询任意区间的和。
即:sum[i]=num[1]+num[2]+……+num[i]
2 差分
差分是前缀和的逆操作,主要用于高效地对数组的某个区间进行批量增减操作。就是数组中每个数减去前面一个数的差(除第一个为原来的数外)
即:cf[i]=num[i]-num[i-1]
初始为全 0(因初始次数为 0)- 区间[a,b]
加 1:d[a]++
,d[b+1]--
- 单点x
加 1:等价于区间[x,x]
,即d[x]++
,d[x+1]--
3 在题目中的运用场景
若直接遍历更新 “被撒次数”:
- 情侣不相邻时,区间
[i+1,j-1]
可能包含上千个元素,每次更新需 O (k) 时间(k 为区间长度),N=5e4 时总复杂度可能达 O (N*M),超时风险极高。 - 差分将所有更新操作(区间 / 单点)压缩为2 次数组赋值(O (1)),最终通过 1 次前缀和(O (M))还原结果,总复杂度降至 O (N+M)。
三、哈希表和差分协同解题
1. 联动逻辑
步骤 | 依赖知识点 | 核心操作 |
---|---|---|
1. 判断情侣是否在场 | 哈希表(pos) | 通过pos.find(s1) 和pos.find(s2) 确认情侣双方是否在座位表中,排除无效情侣。 |
2. 确定更新位置 | 哈希表(pos) | 从pos 中获取情侣的座位索引i 和j ,确定区间[i+1,j-1] 或单点i-1/j+1 。 |
3. 高效更新次数 | 差分(diff 数组) | 对确定的区间 / 单点执行 O (1) 的差分更新,记录被撒狗粮的变化量。 |
4. 还原最终次数 | 前缀和 | 遍历 diff 数组计算前缀和,得到每个位置的实际被撒次数,关联到单身狗 ID。 |
2. 算法时间复杂度分析
- 哈希表操作:插入、查找均为 O (1),总时间 O (N+M)(N 对情侣,M 个座位);
- 差分操作:N 对情侣的更新均为 O (1),总时间 O (N);
- 前缀和操作:遍历 M 个位置,总时间 O (M);
- 整体复杂度:O (N+M),可轻松处理题目中 N≤5e4、M≤8e4 的大数据量。
四、完整解题流程回顾
-
输入处理:
- 读取 N 对情侣,用
couple_map
存储双向映射,couples
保存所有情侣对; - 读取 M 个座位 ID,用
seat
存储顺序,pos
存储 ID→座位索引映射。
- 读取 N 对情侣,用
-
标记单身狗:
- 遍历每个座位,调用
is_single
判断是否为单身狗,用single_arr
(0/1 数组)标记。
- 遍历每个座位,调用
-
差分更新:
- 遍历每对情侣,若双方在场,根据坐次(相邻 / 不相邻)执行差分更新(区间 / 单点)。
-
前缀和统计:
- 计算 diff 数组的前缀和,得到每个座位的被撒次数,用
count_map
存储单身狗 ID→被撒次数。
- 计算 diff 数组的前缀和,得到每个座位的被撒次数,用
-
结果处理:
- 找到
count_map
中的最大次数,收集所有对应 ID,按字典序(即 ID 升序)排序后输出。
- 找到
五、常见错误与避坑点
-
向量越界:
- 初始
seat
向量时,若直接cin >> seat[i]
(未push_back
),会因向量为空导致越界;需用seat.push_back(s)
动态添加元素。 - 差分数组
diff
大小需为m+2
,避免d[b+1]
(如b=m-1
时b+1=m
)越界。
- 初始
-
哈希表查找遗漏:
- 判断情侣是否在场时,需同时检查
s1
和s2
是否在pos
中,仅一方在场时不执行更新。
- 判断情侣是否在场时,需同时检查
-
差分更新边界判断:
- 情侣相邻时,更新
i-1
需判断left>=0
,更新j+1
需判断right<m
,避免越界更新。
- 情侣相邻时,更新
-
结果排序:
- 单身狗 ID 为 5 位字符串,直接用
sort(result.begin(), result.end())
即可按 ID 升序排序(字符串字典序与数字升序一致)。
- 单身狗 ID 为 5 位字符串,直接用