CF1648D 简单清楚的做法
CF1648D 简单清楚的做法
我自己做这题没做出来,看网上题解都有点难看懂,自己搞一个
前置知识
线段树维护: 对于两个序列 ,给定 ,询问:。
区间维护: 的最大值, 的最大值,区间答案。
考虑 ,如果都在两边,调用左右两边的ans,否则就是左边 最大值 右边 最大值。
正片
显然我们只需要关心什么时候进第二行和什么时候出第二行即可。
设 表示走到第二行的 位置,算上解锁区间的代价,最大的收益。答案为 。
(注:后面用 表示区间和, 表示前缀和, 表示1/2/3行)(均为前缀和,没有后缀和)
考虑 的转移。注意到 这个位置一定有一个区间覆盖到它。考虑覆盖 的区间 。
- 转移case1 它不是第一个解锁区间
如果它不是第一个解锁的区间,那我们可以直接从 转移过来,而不需要枚举 转移。
原因是不管 取在哪,最后的总和一定都是:第二行选择的区间和 解锁区间的代价和。因此没必要枚举 ,直接取 就行了。
这个情况的转移为 。
- 转移case2 它是第一个区间
这回没法直接继承了,枚举 是必要的。枚举 表示从 位置进入第二行。
这个情况的转移为 。
- 边界
注意到我们不需要给转移2设置边界,它本身就相当于边界(与其说是转移,它更像是在求一个式子的值,因为它并不依赖于 )。而转移 则强制要求当前的 不是第一个区间,从而 从 转移过来是不合法的。因此我们要令 。
注: 表示用 更新 ,这里是取 。
它的复杂度显然不对,考虑优化。
首先我们显然可以用扫描线相关技巧动态维护当前包含 的区间 的集合。就是枚举到 时,加入 的区间,删除 的区间。
- 优化转移1
变成 。要么只和 有关,要么只和 有关。
我们要求 并且支持加入和删除,用 multiset 维护所有包含 的区间中, 的集合即可。
- 优化转移2
变成 。其中 。
对于同一个 ,我们只关注 的最小值(即 最大值)。把项提取一下,设 ,。它相当于 。
对于每个 维护一个 multiset 表示所有 的 中, 的集合。当我们加入/删除区间时,先更新这个multiset,然后这里的最大值就是新的 ,在线段树上单点修改 更新即可。注意 不需要更新。
然后 就是 加上线段树上 的答案。
这样的话操作之后改multiset和线段树,都是一个 。
复杂度 。
评价
看了其它题解,线段树里都有很多东西,维护一堆复杂的tag。这里用multiset干了许多活,减少了线段树的负担。注意到这里的线段树就一个单点改和一个查询。
当时间空间不卡的时候,尽量用STL替代手写功能。用STL只要稍微想一下就不容易出错了,自己写的话有时要小心更多细节。
代码
// 注:NNF 表示 -INF (Negative INF)
#include<bits/stdc++.h>
using namespace std;
#define N 1000006
#define int long long
#define F(i,l,r) for(int i=(l);i<=(r);++i)
#define D(i,r,l) for(int i=(r);i>=(l);--i)
#define MEM(x,a) memset(x,a,sizeof(x))
int I(){int x=0,f=0;char c=getchar(); while(!isdigit(c))f=(c=='-'),c=getchar(); while(isdigit(c))x=x*10+c-'0',c=getchar(); return f?-x:x;}
#define vi vector<int>
#define eb emplace_back
#define sz(x) ((int)x.size())
#define al(x) x.begin(),x.end()
#define pii pair<int,int>
#define fi first
#define se second
#define lwrb lower_bound
#define uprb upper_bound
#define mset multiset<int>
#define t3 tuple<int,int,int>
const int INF=1e18,NNF=-1e18;
void del1(mset&s,int x){auto it=s.find(x);if(it!=s.end())s.erase(it);}
int gmx(mset&s) {return *prev(s.end());}
int n,m; int a[4][N],s[4][N];
vector<t3> rg;
struct nd{int l,r,k;}r[N];
vi ad[N],dc[N];
mset rec1,rec2[N];
int A[N],B[N];
struct info{int ma,mb,mx;};
info operator+(info a,info b){return (info){max(a.ma,b.ma),max(a.mb,b.mb),max({a.mx,b.mx,a.ma+b.mb})};}
struct SegT
{
#define ls u<<1
#define rs u<<1|1
#define ll ls,L,o
#define rr rs,o+1,R
info a[N];
void up(int u) {a[u]=a[ls]+a[rs];}
void build(int u=1,int L=1,int R=n)
{
if(L==R) {a[u]=(info){A[L],B[L],A[L]+B[L]}; return;}
int o=(L+R)>>1; build(ll);build(rr);up(u);
}
void ca(int p,int x,int u=1,int L=1,int R=n)
{
if(L==R) {a[u].ma=x; a[u].mx=a[u].ma+a[u].mb; return;}
int o=(L+R)>>1;
if(p<=o)ca(p,x,ll); else ca(p,x,rr); up(u);
}
info qr(int l,int r,int u=1,int L=1,int R=n)
{
if(l<=L && R<=r)
{
return a[u];
}
int o=(L+R)>>1;
if(r<=o)return qr(l,r,ll); if(o<l) return qr(l,r,rr);
return qr(l,r,ll)+qr(l,r,rr);
}
}T;
int f[N];
void flandre()
{
n=I(),m=I();
F(i,1,3)
{
F(j,1,n) a[i][j]=I();
F(j,1,n) s[i][j]=s[i][j-1]+a[i][j];
}
F(i,1,m) {int l=I(),r=I(),w=I(); rg.eb(l,r,w);}
F(i,0,sz(rg)-1) {auto [l,r,w]=rg[i]; ad[l].eb(i); dc[r+1].eb(i);}
rec1.insert(NNF); F(i,1,n) rec2[i].insert(NNF);
F(i,1,n) A[i]=gmx(rec2[i]),B[i]=s[1][i]-s[2][i-1];
T.build(); f[0]=NNF;
F(i,1,n)
{
for(auto x:ad[i])
{
auto [l,r,w]=rg[x];
rec1.insert(f[l-1]-s[2][l-1]-w);
rec2[l].insert(-w);
A[l]=gmx(rec2[l]); T.ca(l,A[l]);
}
for(auto x:dc[i])
{
auto [l,r,w]=rg[x];
del1(rec1,f[l-1]-s[2][l-1]-w);
del1(rec2[l],-w);
A[l]=gmx(rec2[l]); T.ca(l,A[l]);
}
f[i]=gmx(rec1)+s[2][i];
f[i]=max(f[i],T.qr(1,i).mx+s[2][i]);
}
int ans=NNF;
F(i,1,n) ans=max(ans,f[i]+s[3][n]-s[3][i-1]);
printf("%lld\n",ans);
}
#undef int
int main()
{
int t=1;
while(t--)flandre();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效