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

浙公网安备 33010602011771号