「CF1034D」Intervals of Intervals 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17367616.html ,转载请注明出处。
传送门
「CF1034D」Intervals of Intervals
题目大意
有 个线段,第 个是 。
定义区间 的价值是第 个线段到第 个线段的并的长度。
找出 个不同的区间,使得总价值最大。输出最大总价值。
。
思路
我们肯定会选出价值前 大的区间。因为 很大,所以我们可以尝试二分价值第 大的区间的价值。
我们设二分的值为 ,那么问题就变为了求价值大于等于 的区间个数。
要求解这个问题,我们肯定需要一种算法,能够处理到所有区间的价值。
因为是求线段并集,我们考虑转化为染色问题,第 个线段的颜色为 ,没有被染色的位置颜色是 。
我们按 到 的顺序依次加入线段 ,那么对于任意 ,区间 的价值就是当前颜色编号大于等于 的位置个数。
如果我们记录 为区间 的价值,那么其实这就是一个 数组的区间加法。这可以使用树状数组直接维护。
因为染色的过程是区间赋值,可以使用 ODT 维护。
ODT(Old Driver Tree,俗称珂朵莉树),就是把具有相同值 的一段连续区间 压成一个三元组 。三元组存入 set,维护区间修改时暴力合并分裂区间即可。
这样我们就用 的时间完成了所有区间价值的处理。
但是注意我们的问题是求价值大于等于 的区间个数。
容易发现,区间的价值(设为 )具有一定的单调性:。
也就是说,对于每个 ,如果区间 满足条件,那么 ,区间 满足条件;且 单调递增时, 单调不减。
所以我们可以使用双指针,找出最后一个价值大于等于 的区间,这样我们就可以用 的时间统计区间个数。
所以我们的总体复杂度就可以做到 。
显然这样的复杂度是不能通过的。我们需要优化。
可以发现,其实我们可以只跑一次 ODT,记录修改操作。然后在二分 时,用差分数组重做这些操作就好了,这样就少了一个 。
然后就做完了。复杂度 。
代码实现
可以把二分的 check 和求答案放在一起写。
注意变量名不要写错了。
check 的写法需要注意。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll n,k;
ll a[N],b[N];//线段
struct node{
ll l,r,c;
bool operator<(const node&a)const{
return l<a.l||l==a.l&&r<a.r;
}
};
set<node>s;//ODT
vector<node>op[N];//记录原始的区间,用于覆盖的区间就是(a[i],b[i],i),无需记录
void init(){
s.insert({0,(ll)1e9,0});//一开始都是0
For(i,1,n){
auto it=s.lower_bound({a[i],(ll)1e9,0});//找到一条过点a[i]的色块
--it;
ll l=it->l,r=it->r,c=it->c;
//整条线段被色块包含
if(r>=b[i]){
s.erase(it);
op[i].pb({a[i],b[i],c});
if(l<a[i])s.insert({l,a[i]-1,c});
if(r>b[i])s.insert({b[i]+1,r,c});
s.insert({a[i],b[i],i});
continue;
}
//线段最左端的色块
s.erase(it++);
op[i].pb({a[i],r,c});
if(l<a[i])s.insert({l,a[i]-1,c});
//线段包含的色块
while(it->r<b[i]){
op[i].pb(*it);
s.erase(it++);
}
//线段最右端的色块
l=it->l,r=it->r,c=it->c;
s.erase(it);
op[i].pb({l,b[i],c});
if(r>b[i])s.insert({b[i]+1,r,c});
s.insert({a[i],b[i],i});
}
}
ll ans;
ll d[N];//差分数组
ll check(ll mid){
ans=0;//价值大于等于mid的区间价值和
ll s=0;//差分数组L位置到R位置的总和
ll now=0;//差分数组1位置到L-1位置代表的区间价值和
ll res=0;//价值大于等于mid的区间数量
ll L=1;//左端点
For(R,1,n){//右端点
d[R]=b[R]-a[R]+1;//预处理为长度
s+=d[R];
for(auto j:op[R]){
ll l=j.l,r=j.r,c=j.c;
d[c]-=r-l+1;//差分
if(c>=L)s-=r-l+1;
if(c&&c<L)now-=c*(r-l+1);
}
while(s>=mid){
s-=d[L];
now+=L*d[L];
L++;//左端点移动
}
ans+=now+s*(L-1);//统计答案
res+=L-1;//统计个数
}
ans-=mid*(res-k);//我们只需要求前k大的总和,多出来的区间减掉
return res>=k;
}
void mian(){
scanf("%lld%lld",&n,&k);
For(i,1,n){
scanf("%lld%lld",&a[i],&b[i]);
b[i]--;//转成闭区间
}
init();
ll l=0,r=1e9,len=0;
while(l<=r){
ll mid=l+r>>1;
if(check(mid)){
len=mid;
l=mid+1;
}else r=mid-1;
}
check(len);
printf("%lld",ans);
}
int main(){
int T=1;
// scanf("%d",&T);
while(T--)mian();
return 0;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现