2025.2.21的模拟赛题“糖果”题解
2025.2.21的模拟赛题“糖果”题解
一道很神奇的李超线段树题目。
这道题难在模糊的性质,可能的思路,潜在的做法太多了,引人误入歧途。但像 nalemy 这样的巨佬又可以一眼看出正确的方向。看出只能说菜就多练吧。
其实很多东西比如“没有糖果的空白段”,“每个位置出现的时间”,“有糖果的段”似乎等等都是线段最值的问题,可以维护的东西太多了,但好像又怎么都不行,这时我们从答案看起。
(前言终)
我们不难发现答案是一个分段一次函数且分的不超过
可以发现每个右端点的位置是一个一次函数即一条直线,而每段真正的右端点是去最大值,在不考虑两个糖果中途重叠在一起的情况下,一个糖果只会成为最大值一次,于是我们合理猜测加上上合并后最大值的切换次数也
为了维护答案解析式的变化,我们应该在不断维护出那些右端点最早的失效时间并将其更新,这可以用一个堆维护。失效时间用二分求出。
为了求每段每个位置
两个糖果连续段接触在一起,也会使解析式变化,我们应当用李超线段树合并解决。
复杂度
代码如下(这是重点):
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=3e5+5,INF=0x3f3f3f3f;
int n,m;
struct Line{
int k,b;
}lin[NN];
struct Candy{
int l,r,v;
void read(){
cin>>l>>r>>v;
return;
}
bool operator<(const Candy &b)const{
return l<b.l;
}
}candy[NN];
set<int> ms;//连续段的L端点
unordered_map<int,int> rt,nowid;//根据连续段的L端点获取李超线段树的根
int tl,tr,tk;
struct ElementInQueue{
int linid,itsL,endtime;
bool operator<(const ElementInQueue &b)const{
return endtime>b.endtime;//小根堆
}
};
priority_queue<ElementInQueue> q;
struct Query{
int t,id;
bool operator<(const Query &b)const{
return t<b.t;
}
void read(int i){
cin>>t;
id=i;
return;
}
}qry[NN];
int ans[NN];
struct SegTrNode{
int ls,rs,lid;
#define ls(x) sgt[x].ls
#define rs(x) sgt[x].rs
#define lid(x) sgt[x].lid
}sgt[NN*30];
int segtot;
int Val(int lid,int x){
if(!lid)return -INF;
return lin[lid].k*x+lin[lid].b;
}
void Insert(int &p,int lid,int L=0,int R=1e6){//动态开点李超线段树的线段全局插入
if(!p)p=++segtot;
if(!lid(p)){
lid(p)=lid;
return;
}
int mid=L+R>>1;
if(Val(lid(p),mid)<=Val(lid,mid))swap(lid(p),lid);
if(L==R)return;
if(Val(lid(p),L)<Val(lid,L))Insert(ls(p),lid,L,mid);
if(Val(lid(p),R)<Val(lid,R))Insert(rs(p),lid,mid+1,R);
return;
}
int Merge(int lt,int rt,int L=0,int R=1e6){
if(!lt||!rt)return lt|rt;
Insert(lt,lid(rt),L,R);
if(L==R)return lt;
int mid=L+R>>1;
ls(lt)=Merge(ls(lt),ls(rt),L,mid);
rs(lt)=Merge(rs(lt),rs(rt),mid+1,R);
return lt;
}
int Max(int l1,int l2,int pos){
if(Val(l1,pos)<Val(l2,pos))return l2;
return l1;
}
int QueryLine(int p,int pos,int L=0,int R=1e6){//查询某个自变量的某个线段树的最大值的那条线段
if(!p)return 0;
if(L==R)return lid(p);
int mid=L+R>>1;
if(pos<=mid)return Max(lid(p),QueryLine(ls(p),pos,L,mid),pos);
else return Max(lid(p),QueryLine(rs(p),pos,mid+1,R),pos);
}
int TouchTime(int L,int lid){//超过1e6一律按1e6算
if(ms.upper_bound(L)==ms.end())return 1e6;//后面没有了,再也不会相撞
int nxtL=*ms.upper_bound(L);
return (nxtL-lin[lid].b)/lin[lid].k;//直接整除就好了
}
int SearchEndTime(int L,int lid,int begintime){//超过1e6一律按1e6算
int l=begintime-1,r=TouchTime(L,lid);//l<begintime表示没有出现
while(l<r){
int mid=l+r+1>>1;
if(QueryLine(rt[L],mid)==lid)l=mid;
else r=mid-1;
}
return l;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)candy[i].read();
sort(candy+1,candy+1+n);
int R=0,L=0;
for(int i=1;i<=n;i++){
if(candy[i].l<=R){
R=max(R,candy[i].r);
lin[i].k=candy[i].v,
lin[i].b=candy[i].r;
Insert(rt[L],i);//在这个李超线段树上插入一条线段
}else{//一个新的连续段
L=candy[i].l;R=candy[i].r;
lin[i].k=candy[i].v,
lin[i].b=candy[i].r;
Insert(rt[L],i);//在这个李超线段树上插入一条线段
ms.insert(L);
}
}
//先得到时间t=0时每个部分的右端点
for(int o:ms){
tl+=o;
int linid=QueryLine(rt[o],0);
nowid[o]=linid;
tk+=lin[linid].k;
tr+=lin[linid].b;
int endtime=SearchEndTime(o,linid,0);//在写SearchEndTime函数的时候注意包含进TounchTime
q.push({linid,o,endtime});
}
for(int i=1;i<=m;i++)qry[i].read(i);
sort(qry+1,qry+1+m);
for(int i=1;i<=m;i++){
while(!q.empty()&&q.top().endtime<qry[i].t){//这里是"<"因为我们把实数时间下取整,EndTime的那一瞬间依然是其本身最大
ElementInQueue ele=q.top();q.pop();
if(!ms.count(ele.itsL))continue;//已经被删了就不处理了
if(ele.endtime==TouchTime(ele.itsL,ele.linid)){//因为碰撞而消除,此时应该合并两个李超线段树
int nxtL=*ms.upper_bound(ele.itsL);
tr-=lin[nowid[nxtL]].b,
tk-=lin[nowid[nxtL]].k;
tl-=nxtL;//后继
nowid.erase(nxtL);
ms.erase(nxtL);
rt[ele.itsL]=Merge(rt[ele.itsL],rt[nxtL]);
rt.erase(nxtL);
}//否则只是简单的更换线段,并且这里是更新到下一条而非直接更新到k这瞬间,可能因为是不好处理吧
tr-=lin[ele.linid].b,
tk-=lin[ele.linid].k;
int newlinid=QueryLine(rt[ele.itsL],ele.endtime+1);
tr+=lin[newlinid].b,
tk+=lin[newlinid].k;
nowid[ele.itsL]=newlinid;
q.push({newlinid,ele.itsL,SearchEndTime(ele.itsL,newlinid,ele.endtime+1)});
}
ans[qry[i].id]=tk*qry[i].t+tr-tl;
}
for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";
return 0;
}/*
这道题根本不是在维护r,而是在维护r的解析式,通过r的解析式和并得到答案的函数
通过处理r直线的变换维护答案函数的变化
*/
作者:lupengheyyds
出处:https://www.cnblogs.com/lupengheyyds/p/18730116
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】