题解:P14660 你不孤单,我们都在

· · 题解

更好的阅读体验

题意

我们有 n 个朋友,每个朋友有:

当前压力值 a_i

最大承受值 b_i

我们可以选择至多一次倾诉活动:

选择任意一些人参加。

所有被选中的人的压力值会变成 这些人的压力值的平均值。

目标:活动后,所有人满足 a_i \leq b_i

关键转化

如果不做活动,那么要求:\forall i, a_i \leq b_i

如果已经满足,那么直接输出 YES

如果有一些人不满足(即 a_i > b_i),那么必须通过一次活动来尝试解决。

设参加活动的人的集合为 S,人数为 |S|,原来压力值总和为 sum_S,那么活动后,这些人的压力值都变为:

如果 $a_i > b_i$,那么这个人 必须 参加活动,因为如果不参加,他仍然 $a_i > b_i$,不可能满足条件。 设必须参加的人集合为 $M$,$sum_M$ 为他们的压力值之和,$|M|$ 为人数。 活动后,对于参加的人: $\frac{sum_S}{|S|} \leq \min b_i$。 因为参加的人压力值都变成平均值,要满足每个人的 $b_i$ 限制,必须平均值 $\leq$ 每个人最小 $b_i$ 值。 对于不参加的人: $a_i \leq b_i

这本来就是 a_i \leq b_i 的人,活动前就满足。

我们已知:

可以从 $a_i \leq b_i$ 的人中选一些人加入活动,记为集合 $O$。 设 $O$ 的 $a$ 总和为 $sum_O$,人数为 $|O|$。 条件: $\frac{sum_M + sum_O}{|M| + |O|} \leq \min b_i$。 设 $B_{min} = \min_{i \in S} b_i$,则条件等价于: $sum_M + sum_O \leq B_{min} \times (|M| + |O|)$。 整理得: $sum_M - B_{min} \times |M| \leq \sum (B_{min} - a_i)$。 由于 $M$ 中每个人 $a_i > b_i \geq B_{min}$(因为 $B_{min}$ 是 $S$ 中最小的 $b_i$),所以左边是负数,更容易满足。 ## 贪心策略 为了最大化右边 $\sum (B_{min} - a_i)$,我们应该选择那些 $a_i \leq B_{min}$ 且 $a_i \leq b_i$ 的人加入 $O$,且 $a_i$ 越小越好(因为 $B_{min} - a_i$ 越大)。 ## 算法步骤: 把所有人分为两类: 必须参加的人 $M$:$a_i > b_i$。 可选的人 $O$:$a_i \leq b_i$。 如果没有必须参加的人,直接输出 YES。 对可选的人按 $b_i$ 从大到小排序。 遍历所有可能的 $B_{min}$ 值(来自 $M$ 和 $O$ 的 $b_i$)。 对于每个 $B_{min}$,选择所有 $b_i \geq B_{min}$ 的可选人,且只选那些 $a_i \leq B_{min}$ 的(否则平均值可能超过 $B_{min}$)。 检查平均值条件是否满足。 ## 数据结构优化 直接遍历 $O(n^2)$ 会超时,需要优化: 用树状数组维护可选人的 $a_i$ 值。 对 $b_i$ 从大到小扫描,动态加入可选人。 快速查询 $a_i \leq B_{min}$ 的可选人的个数和 $a_i$ 总和。 时间复杂度:$O(n \log n)$。 ## 代码 ```cpp #include <bits/stdc++.h> #define Code using #define by namespace #define jhc std Code by jhc; typedef long long ll; const int N = 100005; pair<ll , ll> must[N] , opt[N]; ll all_a[N] , all_b[N]; int bit_cnt[N]; ll bit_sum[N]; int n , m; //树状数组 void update(int idx , ll val) { for(int i = idx;i <= m;i += i & -i) { bit_cnt[i]++; bit_sum[i] += val; } } ll q_cnt , q_sum; void query(int idx) { q_cnt = 0 , q_sum = 0; for(int i = idx;i > 0;i -= i & -i) { q_cnt += bit_cnt[i]; q_sum += bit_sum[i]; } } bool solve() { int n; scanf("%d" , &n); int must_cnt = 0 , opt_cnt = 0; ll sumM = 0 , cntM = 0 , minB_M = 1e18; for(int i = 0;i < n;i++) { ll a , b; scanf("%lld %lld" , &a , &b); if(a > b) { must[must_cnt++] = {a , b}; sumM += a; cntM++; if(b < minB_M) { minB_M = b; } } else { opt[opt_cnt++] = {a, b}; } } if(must_cnt == 0) { return true; } //贪心 sort(opt , opt + opt_cnt , [](auto &p1 , auto &p2) { return p1.second > p2.second; }); int a_cnt = 0; for(int i = 0;i < opt_cnt;i++) { all_a[a_cnt++] = opt[i].first; } sort(all_a , all_a + a_cnt); m = unique(all_a , all_a + a_cnt) - all_a; for(int i = 1;i <= m;i++) { bit_cnt[i] = 0; bit_sum[i] = 0; } int b_cnt = 0; for(int i = 0;i < must_cnt;i++) { all_b[b_cnt++] = must[i].second; } for(int i = 0;i < opt_cnt;i++) { all_b[b_cnt++] = opt[i].second; } sort(all_b , all_b + b_cnt); b_cnt = unique(all_b , all_b + b_cnt) - all_b; reverse(all_b, all_b + b_cnt); int idx = 0; for(int i = 0;i < b_cnt;i++) { ll minB = all_b[i]; if (minB > minB_M) continue; while(idx < opt_cnt && opt[idx].second >= minB) { ll a = opt[idx].first; int pos = lower_bound(all_a , all_a + m , a) - all_a + 1; update(pos , a); idx++; } int pos = upper_bound(all_a , all_a + m , minB) - all_a; query(pos); ll total_cnt = cntM + q_cnt; ll total_sum = sumM + q_sum; if(total_sum <= minB * total_cnt) { return true; } } return false; } int main() { int T; scanf("%d" , &T); while(T--) { printf(solve() ? "YES\n" : "NO\n"); } return 0; } ```