题解:P16370 [OOI 2026] Monsters and Swords
更好的阅读体验
来讲一个非常自然的
考虑转为一个最优化问题:如果我能求出整个过程中至少花费的钱数
那么这题本质上是要把所有怪物分段,每一段用一把剑来消灭。假设该段内怪物的
简单解释一下
接下来我们优化这个 dp。发现我们的 dp 转移的
现在假设考虑区间
但是极值分治并没有和直接按照中点分治那样优良的性质,因为极值分治的分治树可能很高,如果每一次都付出
因此只需要对
-
若
r - mid < mid - l ,则我们直接枚举i ,查询最优的j 来更新f_i 。那么我们需要查询满足
i - j \le k 且x + sum_j - f_j \ge \operatorname{cost}(i, j) 的最小的f_j 。容易发现f 单调不降,因此查询出满足上式的最小j 即可。我们维护一个线段树,在j 位置存储x + sum_j - f_j 的值,线段树上二分出第一个\ge \operatorname{cost}(i, j) 的位置即可。 -
若
r - mid \ge mid - l ,则我们枚举j ,处理每个j 对一段区间内的f_i 的贡献。那么首先这个
j 也要满足x + sum_j - f_j \ge \operatorname{cost}(i, j) 。在此基础上,这个j 会对所有i \isin [mid, r] ,且i - j \le k 的i 产生贡献,贡献显然是f_j + \operatorname{cost}(i, j) 。则维护一个支持区间取\min 单点查询的数据结构即可,在我的实现中选用了线段树。
至此问题得到解决,复杂度为分治的一只
#include<bits/stdc++.h>
#define endl '\n'
#define N 500006
using namespace std;
using i64=long long;
constexpr i64 INF=2e15;
template <class T1,class T2> inline void chkmin(T1 &x,T2 y) {x=x<y?x:y;}
template <class T1,class T2> inline void chkmax(T1 &x,T2 y) {x=x<y?y:x;}
int n,m,k,x,maxn,a[N],b[N],suf[N],cost[N];
int tp,ch[N][2],stk[N],val[N];
i64 f[N],sb[N];
pair<int,int> c[N];
struct Segtree1 { // 单点修改,区间查 min
i64 tree[N<<2];
void build(int p,int l,int r)
{
tree[p]=-INF; if(l==r)return;
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}
void update(int p,int l,int r,int k,i64 x)
{
if(l==r)return tree[p]=x,(void)0;
int mid=l+r>>1;
k<=mid?update(p<<1,l,mid,k,x):update(p<<1|1,mid+1,r,k,x);
tree[p]=max(tree[p<<1],tree[p<<1|1]);
}
int find(int p,int l,int r,i64 x)
{
if(l==r)return l;
int mid=l+r>>1;
return tree[p<<1]<x?find(p<<1|1,mid+1,r,x):find(p<<1,l,mid,x);
}
void find(int p,int l,int r,int L,int R,i64 x,int &res)
{
if(res!=-1)return;
if(L<=l&&r<=R)
{
if(tree[p]>=x)res=find(p,l,r,x);
return;
}
int mid=l+r>>1;
if(L<=mid)find(p<<1,l,mid,L,R,x,res);
if(R>mid)find(p<<1|1,mid+1,r,L,R,x,res);
}
} T1;
struct Segtree2 { // 区间 chkmin,单点查询
i64 tree[N<<2];
void build(int p,int l,int r)
{
tree[p]=INF; if(l==r)return;
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}
void update(int p,int l,int r,int L,int R,i64 x)
{
if(L<=l&&r<=R)return chkmin(tree[p],x);
int mid=l+r>>1;
if(L<=mid)update(p<<1,l,mid,L,R,x);
if(R>mid)update(p<<1|1,mid+1,r,L,R,x);
}
i64 query(int p,int l,int r,int k,i64 x)
{
chkmin(x,tree[p]); if(l==r)return x;
int mid=l+r>>1;
return k<=mid?query(p<<1,l,mid,k,x):query(p<<1|1,mid+1,r,k,x);
}
} T2;
void solve(int mid,int l,int r)
{
if(l>r)return;
solve(ch[mid][0],l,mid-1);
if(r-mid<mid-l)
for(int i=mid;i<=r;i++)
{
int lb=max({0,l-1,i-k}); if(lb>=mid)break;
int p=-1; T1.find(1,0,n,lb,mid-1,cost[mid],p);
if(p!=-1)chkmin(f[i],f[p]+cost[mid]);
}
else for(int i=max(0,l-1);i<=mid-1;i++)
{
if(i+k<mid||x+sb[i]-f[i]<cost[mid])continue;
T2.update(1,0,n,mid,min(i+k,r),f[i]+cost[mid]);
}
chkmin(f[mid],T2.query(1,0,n,mid,INF));
T1.update(1,0,n,mid,x+sb[mid]-f[mid]);
solve(ch[mid][1],mid+1,r);
}
main()
{
scanf("%d%d%d%d",&n,&m,&k,&x);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&b[i]),chkmax(maxn,a[i]),sb[i]=sb[i-1]+b[i];
for(int i=1;i<=m;i++)scanf("%d%d",&c[i].first,&c[i].second);
sort(c+1,c+1+m),suf[m+1]=2e9;
if(maxn>c[m].first)return printf("No\n"),0;
for(int i=m;i;i--)suf[i]=min(suf[i+1],c[i].second);
for(int i=1;i<=n;i++)
{
cost[i]=suf[lower_bound(c+1,c+1+m,make_pair(a[i],0))-c];
while(tp&&a[i]>val[tp])ch[i][0]=stk[tp--];
if(tp)ch[stk[tp]][1]=i;
stk[++tp]=i,val[tp]=a[i];
}
for(int i=0;i<=n;i++)f[i]=INF;
T1.build(1,0,n),T2.build(1,0,n),f[0]=0,solve(stk[1],0,n);
printf(f[n]<=x+sb[n]?"Yes\n":"No\n");
return 0;
}