Luogu P4064 [JXOI2017] 加法 题解 [ 蓝 ] [ 二分 ] [ 贪心 ] [ 差分 ]

加法:感觉挺像超速检测风格的。

最大化最小值,显然可以二分最小值,判断最小值是否合法。这样我们求求出了每个数至少要覆盖的次数。

于是问题被转化为,\(m\) 个区间里选出最少个区间,判断使用的区间个数是否 \(\le k\)

这个问题显然是一个最小区间覆盖的贪心,我们从左向右扫。假设当前扫到的下标为 \(x\),那么将 \(L_i \le x\) 的区间加入集合里。如果 \(x\) 处需要覆盖,那么从集合里选出 \(R_i\) 最大的区间覆盖即可。因为 \(L_i\) 左边的已经全部满足了,那我尽可能选右端点最大的,覆盖更多的区域一定更优。差分模拟即可,时间复杂度 \(O(n\log V \log n)\)

总结一下此类区间贪心的板子:

  • 给定 \(n\) 个点,\(k\) 个区间,求最少需要多少个点才能覆盖所有区间。
    • 对所有区间按右端点排序,假设当前第一个没有被覆盖的区间的右端点为 \(R\),找到 \(pos\) 最大的且在 \(R\) 左边的点覆盖掉即可。
  • 给定 \(n\) 个区间,\(k\) 个点,求最少需要多少个区间才能覆盖所有点。
    • 将所有点排序,依次遍历所有点。假设当前遍历到的点坐标为 \(X\),则将所有 \(L\le X\) 的区间加入集合中。如果该点尚未被覆盖,则选择集合内 \(R\) 最大的区间进行覆盖。
  • 给定 \(n\) 个区间,求最大独立集(最大化选择的区间,并使得选出的区间无交集)。
    • 将区间按照右端点排序,顺着扫过去,能选则选。
  • 给定 \(n\) 个区间,将区间分为若干组,使得组内任意两个区间无交,求出最少分组数。
    • Sol.1:区间按照左端点排序,然后对于每一个区间选择右端点最小的组加入,如果无法加入则单开一组。
    • Sol.2:区间按照右端点排序,然后对于每一个区间选择最大的小于该区间左端点的组加入,如果无法加入则单开一组。需要这样选的原因是每个区间都必须分到某一组,而如果每次都选右端点最小的组加入会导致一些左端点大,右端点小的区间占用了右端点最小的组,导致答案偏大。
  • 给定 \(n\)\(A\) 区间,\(m\)\(B\) 区间,选择最少个数的 \(A\) 区间,使得 \(A\) 区间之并覆盖所有的 \(B\) 区间。
    • \(B\) 区间转化为点,按照普通的区间覆盖点的做法做即可。
  • 给定 \(n\) 个区间,求并集。
    • 将区间按照左端点排序,依次扫过去,判断和前面的区间是否有交,有交则拓展右端点即可。
  • 给定 \(n\) 个区间,只留下不包含任何其他区间的区间。
    • 将区间按照左端点、右端点的双关键字排序,依次扫过去,用栈维护当前保留的区间,若保留的区间不合法则弹出,推入栈时需要注意本次推入是否合法。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N = 200005;
ll n, m, k, ad, a[N], b[N], f[N];
pi rg[N];
bool check(ll mn)
{
    for(int i = 1; i <= n; i++)
    {
        b[i] = max(0ll, ll(ceil(1.0 * (mn - a[i]) / ad)));
        f[i] = 0;
    }
    ll res = 0;
    int p = 1;
    priority_queue<int> q;
    for(int i = 1; i <= n; i++)
    {
        f[i] += f[i - 1];
        while(p <= m && rg[p].fi <= i)
        {
            q.push(rg[p].se);
            p++;
        }
        b[i] -= f[i];
        while(b[i] > 0)
        {
            if(q.empty()) return 0;
            int cur = q.top();
            q.pop();
            if(cur < i) return 0;
            f[i]++;
            f[cur + 1]--;
            res++;
            b[i]--;
        }
    }
    return (res <= k);
}
void solve()
{
    cin >> n >> m >> k >> ad;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= m; i++) cin >> rg[i].fi >> rg[i].se;
    sort(rg + 1, rg + m + 1);
    ll l = 1, r = 1e9, mid;
    while(l < r)
    {
        mid = (l + r + 1) >> 1;
        if(check(mid))l = mid;
        else r = mid - 1;
    }
    cout << l << '\n';
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
posted @ 2025-09-06 02:21  KS_Fszha  阅读(9)  评论(0)    收藏  举报