Atcoder Beginner Contest ABC 275 Ex (H) Monster 题解
先明确是一个怪物的血量,是攻击的代价。发现如果我们想攻击一个怪物,不如找出一个极大的包含它的区间,满足这个区间内所有怪物的攻击代价都不大于它本身的代价,因为多攻击一点肯定是好的,反正不多花钱。这启发我们把所有的怪物依照的值建一棵笛卡尔树,其中最大的怪物为根。容易看出一定存在一个最优解,满足每次攻击都是攻击了笛卡尔树上的一个点以及它子树中所有的怪物。我们把对一个点x及其子树内所有点的攻击称为"位于x的攻击"。
令表示已经做完了所有位于i及其子树内的攻击,子树中剩余血量最大的怪物的血量时的最小代价。转移的时候先把当前点的dp值与儿子的dp值合并,即,其中k为i的儿子。接下来考虑位于当前点的怪物,根据定义把满足的都设为无穷大。最后考虑进行一些位于i的攻击,也就是对于所有j,进行的更新操作;为了满足dp数组不增的性质(因为dp的定义是血量的代价),应该从大往小枚举j。但是j的值域很大,这样是没法过的。
固定i,把每个点对都画在坐标系上,发现这些点构成一个下凸函数。证明可以使用归纳法,首先每个叶子结点的dp数组肯定是下凸的,长这样:
非叶节点转移的时候,先合并dp值,众所周知几个下凸函数合并(每个点分别加)后仍是下凸的。最后更新的时候,其实是把图像最左侧几段斜率小于的改成了斜率为的:
我们可以用set来维护斜率的从右到左的变化。在图像的最右段,斜率和函数值都是0。我们在每个点的set中维护一些点对,表示从右往左看,在横坐标为x的位置,函数图像的斜率增加了y。这样的好处是合并dp值很好做,只要把子树的set启发式合并就可以了,如果有横坐标重复的则把对应增加的值相加。在维护set的同时记录每个图像在x=0处的斜率,这样在考虑当前点的怪物以及最后更新时,可以直接在set的最前端erase掉斜率不合格的部分。
时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <LL,LL> #define fi first #define se second #define mpr make_pair #define pb push_back void fileio() { #ifdef LGSs freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif } void termin() { #ifdef LGSs std::cout<<"\n\nPROGRAM TERMINATED"; #endif exit(0); } using namespace std; const LL inf=2e9; LL n,a[100010],b[100010],root,slp0[100010]; vector <LL> g[100010]; set <pii> f[100010]; namespace st { LL n2=1; pii dat[400010]; void build(LL k,LL lb,LL ub) { if(lb==ub) return; build(k+k+1,lb,(lb+ub)/2);build(k+k+2,(lb+ub)/2+1,ub); dat[k]=max(dat[k+k+1],dat[k+k+2]); } pii qry(LL k,LL lb,LL ub,LL tlb,LL tub) { if(ub<tlb||tub<lb) return mpr(0LL,0LL); if(tlb<=lb&&ub<=tub) return dat[k]; return max(qry(k+k+1,lb,(lb+ub)/2,tlb,tub),qry(k+k+2,(lb+ub)/2+1,ub,tlb,tub)); } } LL buildTree(LL lb,LL ub) { pii val=st::qry(0,0,st::n2-1,lb,ub); LL ret=val.se; if(lb<ret) g[ret].pb(buildTree(lb,ret-1)); if(ret<ub) g[ret].pb(buildTree(ret+1,ub)); return ret; } void dfs(LL pos) { if(g[pos].empty()) { slp0[pos]=b[pos]; f[pos].insert(mpr(inf,0));f[pos].insert(mpr(a[pos],b[pos])); return; } rep(i,g[pos].size()) { LL u=g[pos][i]; dfs(u); slp0[pos]+=slp0[u]; if(f[pos].size()<f[u].size()) swap(f[pos],f[u]); for(auto it:f[u]) { auto itt=f[pos].lower_bound(mpr(it.fi,0LL)); if(itt!=f[pos].end()&&itt->fi==it.fi) { LL val=itt->se+it.se; f[pos].erase(itt); f[pos].insert(mpr(it.fi,val)); } else f[pos].insert(it); } } while(f[pos].begin()->fi<=a[pos]) slp0[pos]-=f[pos].begin()->se,f[pos].erase(f[pos].begin()); f[pos].insert(mpr(a[pos],inf));slp0[pos]+=inf; while(true) { //if(f[pos].empty()) cout<<"FUCK",exit(0); LL afterSlp=slp0[pos]-f[pos].begin()->se; if(afterSlp<b[pos]) { LL act=b[pos]-afterSlp,x=f[pos].begin()->fi; f[pos].erase(f[pos].begin());f[pos].insert(mpr(x,act)); slp0[pos]=b[pos]; break; } slp0[pos]=afterSlp; f[pos].erase(f[pos].begin()); } } int main() { fileio(); cin>>n; rep(i,n) scanf("%lld",&a[i]); rep(i,n) scanf("%lld",&b[i]); while(st::n2<n) st::n2*=2; rep(i,n) st::dat[i+st::n2-1]=mpr(b[i],i); st::build(0,0,st::n2-1); root=buildTree(0,n-1); dfs(root); LL slp=0,ans=0; for(auto it=--f[root].end();;--it) { slp+=it->se; if(it==f[root].begin()) ans+=slp*it->fi; else { auto itt=it;--itt; ans+=slp*(it->fi-itt->fi); } if(it==f[root].begin()) break; } cout<<ans<<endl; termin(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话