【Codeforces 1389D】Segment Intersections
题目链接
翻译
给你 \(n\) 对区间,一开始每对区间都是 [l1,r1] 和 [l2,r2]。
每对区间你都可以求他们的交集的长度,长度定义是尾巴减头部 (不用加 \(1\)),每对区间的交集和加起来,要求最后的交集的和大于等于 \(k\)。
并且你可以扩展 \((x,y)\) 成为 \((x-1,y)\) 或 \((x,y+1)\)
问你最少需要多少次这样的扩展操作才能满足要求。
题解
首先交换两个区间,让这两个区间满足 \(l1 <= l2\)。
然后对 \(l2 \le r2\) (有相交部分), 和 \(l2 \gt r2\) (无相交部分$ 进行分类讨论。
具体的讨论步骤写在代码的注释里面了,主要就是贪心的过程。看看是变动新的区间,还是就在这个区间上进行扩展 (完全重合之后,就只能通过同时变动两个区间增加重叠部分了)。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int T;
int n, k,l1,r1,l2,r2;
int main(){
#ifdef LOCAL_DEFINE
freopen("E://9.AlgorithmCompetition//Visitor.txt","r",stdin);
#endif
ios::sync_with_stdio(0),cin.tie(0);
cin >> T;
while (T--){
cin >> n >> k;
cin >> l1 >> r1;
cin >> l2 >> r2;
if (k == 0){
cout << 0 << endl;
continue;
}
if (l1 > l2){
swap(l1,l2);
swap(r1,r2);
}
if (l2 <= r1){
if (r2 <= r1){
//算重叠部分
LL tmp = r2-l2;
tmp*=n;
if (tmp >= k){
//重叠部分就够了
cout << 0 << endl;
}else{
//需要额外的步骤数
LL ans = 0;
//不够,那么求出需要多少
LL need = k-tmp;
//一步就能增加交集的步数
LL onestep = r1-r2+l2-l1;
if (onestep != 0){
//求出需要多少个区间才够
LL needI = need/onestep + (need%onestep!=0);
//但是要和 n 取较小值。
//如果不够
if (needI > n){
//填满 n 个
need -= n*onestep;
ans += n*onestep;
}else{
//够了
//直接加上 need 步,然后让need变成0
ans += need;
need = 0;
}
}
//剩下的都要两步才能增加一个交集
ans += need*2;
cout << ans << endl;
}
}else{
//r2 > r1
//算重叠部分
LL tmp = r1-l2;
tmp*=n;
if (tmp >= k){
cout << 0 << endl;
}else{
// 需要额外步骤数
LL ans = 0;
//还需要的贡献
LL need = k - tmp;
//l2 往左或者r1往右都能操作一次就增加一个贡献
LL onestep = r2 - r1 + l2 - l1;
if (onestep != 0){
// 需要的区间数
LL needI = need / onestep + (need%onestep!=0);
// 若区间数不够
if (needI > n){
//填满 n 个区间
ans += n*onestep;
need -= n*onestep;
}else{
//区间数够,那么直接可以让need清零
ans += need;
need = 0;
}
}
//剩下的部分都只能通过加两次得到了
ans += need*2;
cout << ans << endl;
}
}
}else{
//l2 > r1
//两个区间不相交。
//每个区间都必然有一个操作之后不能增加相交段的过程。
//这一段结束之后,就可以满足操作一次就增加一个相交段,直至到最大的那个区间
//但是之后呢? 是用新的区间还是就两个两个的?
//就两个操作增加一个区间的话,很好算。就是 rest/2 (上取整)
//有用新的区间的情况呢?又或者,用了新的区间之后,在最后那段又两个两个加
//利用新区间增加 x 的代价就为 (l2-r1)+x 其中括号内是常量
//并且这是只用一对区间的情况,x ∈ [0,max(len1,len2)]。
//那么我们可以每 max(len1,len2) 段进行考虑,如果它除 2 比 l2-r1+max(len1,len2) 来的大
//那么这段就用新增加段的形式,否则就用两个一增的形式。
//最后会余下来 rest%max(len1,len2) 需求量
//这部分怎么办?
//看起来这部分也可以用比较的方式做。。
//就除2 和 (l2-r1)+x 比较?
//但是要统计用了多少个区间。。。
//不相交就没有重叠部分了。。
LL need = k;
//需要操作多少次才能重叠
LL gap = l2-r1;
//重叠之后可以最多再增加到的长度。
LL maxl = r2-l1;
// 如果在一个重叠区间内就能搞定,则直接输出代价。
if (need <= maxl){
cout << gap + need << endl;
}else{
//计算操作数
LL ans = 0;
//首先用掉一个区间
ans += maxl+gap;
need -= maxl;
n--;
//对于每个 maxl 的增加量,对比 maxl + gap 和 maxl/2
//直接两个加一个增加量
LL cost1 = maxl*2;
//用新的区间
LL cost2 = maxl + gap;
if (cost2 < cost1){
//如果用新的区间更优
//获取需要的区间个数
LL needI = need/maxl;
//但是不能超过剩余区间个数 n
needI = min(needI,1LL*n);
//需求量满足了 needI*maxl
need -= needI*maxl;
//相应的,代价为needI个区间重叠的代价
ans += cost2*needI;
//区间数减掉needI
n-=needI;
}
//剩余还有 need
//如果need 大于等于 maxl, 说明区间不够了, 直接用两个的
if (need >= maxl || n == 0){
ans += need*2;
}else{
//区间够用, 那么就看最后 need (need < maxl) 用哪个更划算
cost1 = need*2;
cost2 = need + gap;
//不用比较了, 直接加上较小值就好
ans += min(cost1,cost2);
}
cout << ans << endl;
}
}
}
return 0;
}