题解:P10207 [JOI 2024 Final] 马拉松比赛 2 / Marathon Race 2
思路
在考场上我先看了看子任务一,也就是起点和终点均为
但考场上我拿完子任务一的分就跑路了,没继续往下想。其实通过上面的思考不难发现,我们要在速度大的时候(也就是拿的球少的时候)尽量跑远一些的距离。以及如果我们经过一个球多次,最优策略是在最后一次拿它。
有了以上性质,我们不难发现在最优策略下,我们拿的球一定是一段前缀加上一段后缀,未拿的球是中间的一段连续段,第一个拿的球一定是第一个或最后一个。
那么,我们可以设计 dp 状态了,我们先将
考虑如何转移,我们发现当未拿球的区间为
有了状态转移方程,我们就可以进行区间 dp 了,但我们发现这样区间 dp 是
那我们想想,No 了?
我们设去重后的 No 了,算出来大概是
预处理完了,该想想怎么求答案了,对于每种起点和终点,我们需要确定第一个和最后一个拿的球。前者很简单,只有
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,q,s,g,t,ans,sum[500005],x[500005],dp[1005][1005][2][2];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>x[i];
sum[x[i]]++;
}
for(int i=1;i<=m;i++){
sum[i]+=sum[i-1];
}
sort(x+1,x+n+1);
n=unique(x+1,x+n+1)-x-1;
cin>>q;
if(n>=1000){
while(q--){
cout<<"No\n";
}
return 0;
}
memset(dp,0x3f,sizeof(dp));
dp[1][n][0][0]=dp[1][n][1][1]=0;
for(int len=n-1;len>=1;len--){
for(int l=1;l<=n-len+1;l++){
int r=l+len-1;
int s=sum[x[l]-1]+sum[m]-sum[x[r]]+1;
for(int i=0;i<=1;i++){
dp[l][r][i][0]=min(dp[l-1][r][i][0]+s*(x[l]-x[l-1]),dp[l][r+1][i][1]+s*(x[r+1]-x[l]));
dp[l][r][i][1]=min(dp[l-1][r][i][0]+s*(x[r]-x[l-1]),dp[l][r+1][i][1]+s*(x[r+1]-x[r]));
}
}
}
while(q--){
cin>>s>>g>>t;
ans=INT_MAX;
if(g<=x[n]){
int i=lower_bound(x+1,x+n+1,g)-x;
ans=min(ans,dp[i][i][0][0]+abs(s-x[1])+(sum[m]+1)*abs(x[i]-g));
ans=min(ans,dp[i][i][1][0]+abs(s-x[n])+(sum[m]+1)*abs(x[i]-g));
}
if(x[1]<=g){
int i=upper_bound(x+1,x+n+1,g)-x-1;
ans=min(ans,dp[i][i][0][0]+abs(s-x[1])+(sum[m]+1)*abs(x[i]-g));
ans=min(ans,dp[i][i][1][0]+abs(s-x[n])+(sum[m]+1)*abs(x[i]-g));
}
if(ans+sum[m]<=t){
cout<<"Yes\n";
}
else{
cout<<"No\n";
}
}
return 0;
}