Atcoder Beginner Contest ABC 275 Ex (H) Monster 题解

先明确ai是一个怪物的血量,bi是攻击的代价。发现如果我们想攻击一个怪物,不如找出一个极大的包含它的区间,满足这个区间内所有怪物的攻击代价都不大于它本身的代价,因为多攻击一点肯定是好的,反正不多花钱。这启发我们把所有的怪物依照bi的值建一棵笛卡尔树,其中bi最大的怪物为根。容易看出一定存在一个最优解,满足每次攻击都是攻击了笛卡尔树上的一个点以及它子树中所有的怪物。我们把对一个点x及其子树内所有点的攻击称为"位于x的攻击"。

dpi,j表示已经做完了所有位于i及其子树内的攻击,子树中剩余血量最大的怪物的血量j时的最小代价。转移的时候先把当前点的dp值与儿子的dp值合并,即dpi,j+=dpk,j,其中k为i的儿子。接下来考虑位于当前点的怪物,根据定义把满足j<aidpi,j都设为无穷大。最后考虑进行一些位于i的攻击,也就是对于所有j,进行dpi,j1dpi,j+bi的更新操作;为了满足dp数组不增的性质(因为dp的定义是血量j的代价),应该从大往小枚举j。但是j的值域很大,这样是没法过的。

固定i,把每个点对(j,dpi,j)都画在坐标系上,发现这些点构成一个下凸函数。证明可以使用归纳法,首先每个叶子结点的dp数组肯定是下凸的,长这样:

非叶节点转移的时候,先合并dp值,众所周知几个下凸函数合并(每个点分别加)后仍是下凸的。最后更新的时候,其实是把图像最左侧几段斜率小于bi的改成了斜率为bi的:

我们可以用set来维护斜率的从右到左的变化。在图像的最右段,斜率和函数值都是0。我们在每个点的set中维护一些点对(x,y),表示从右往左看,在横坐标为x的位置,函数图像的斜率增加了y。这样的好处是合并dp值很好做,只要把子树的set启发式合并就可以了,如果有横坐标重复的则把对应增加的值相加。在维护set的同时记录每个图像在x=0处的斜率,这样在考虑当前点的怪物以及最后更新时,可以直接在set的最前端erase掉斜率不合格的部分。
时间复杂度O(nlog2n)

点击查看代码
#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();
}
posted @   LegendStane  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示