题目传送门

题意

我们有 \(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\),那么活动后,这些人的压力值都变为:
\(\frac{sum_S}{|S|}\)

如果 \(a_i > b_i\),那么这个人 必须 参加活动,因为如果不参加,他仍然 \(a_i > b_i\),不可能满足条件。

设必须参加的人集合为 \(M\)\(sum_M\) 为他们的压力值之和,\(|M|\) 为人数。

活动后,对于参加的人:
\(\frac{sum_S}{|S|} \leq \min_{i \in S} b_i\)
因为参加的人压力值都变成平均值,要满足每个人的 \(b_i\) 限制,必须平均值 ≤ 每个人最小 \(b_i\) 值。

对于不参加的人:
\(a_i \leq b_i\)
这本来就是 \(a_i \leq b_i\) 的人,活动前就满足。

我们已知:

\(M\) 必须参加

可以从 \(a_i \leq b_i\) 的人中选一些人加入活动,记为集合 \(O\)

\(O\)\(a\) 总和为 \(sum_O\),人数为 \(|O|\)

条件:
\(\frac{sum_M + sum_O}{|M| + |O|} \leq \min_{i \in S} 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_{i \in O} (B_{min} - a_i)\)

由于 \(M\) 中每个人 \(a_i > b_i \geq B_{min}\)(因为 \(B_{min}\)\(S\) 中最小的 \(b_i\)),所以左边是负数,更容易满足。

贪心策略

为了最大化右边 \(\sum_{i \in O} (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)\)

代码

#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;
}