1 条题解
-
0
自动搬运
来自洛谷,原作者为

DaiRuiChen007
白鸟白鸟 不要回头望 你要替我飞去那地方 一去那地方 那是你我共同故乡搬运于
2025-08-24 22:47:15,当前版本为作者最后更新于2023-06-12 16:48:21,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P9312 题解
题目大意
平面直角坐标系上有 个点,坐标为 ,保证 是一个 的排列,对于横坐标相邻的点有线段连接,你的目标是沿着连接相邻两个点的线段遍历这 个点。
有 个区间,第 个区间 有价格 ,需要在 上购买,你能从 移动到 当且仅当 中的每个 都至少被一个你购买的区间覆盖。
对于每个区间 求出:你购买 后从 开始遍历所有点的最小花费。
数据范围:。
思路分析
考虑 dp 状态: 表示购买的线段覆盖了 ,且当前位置在 时继续遍历所有点的最小花费,容易发现知道 和 就可以知道 能到达的整个区间,枚举下一步购买的线段即可。
注意到一个观察:对于一个横坐标的区间 ,想要拓展到 中的所有点需要的 一定是一个包含 的连续区间 ,而剩余的线段可以在以后需要拓展时再购买,因此可以用 表示状态。
考虑进一步简化状态,注意到当我们确定覆盖区间 后,能够拓展的横坐标区间 可能有很多个不相交的段,因此我们需要记录一维 表示我们实际所在的是哪个段。
而注意到 一定是某个最小的 , 一定是某个最大的 ,因此当我们记录 后,横坐标区间 一定经过 ,那么这样的 就是唯一的,此时只需要记录状态 即可。
考虑转移的形式:
$$\begin{aligned} dp_{u,v}&\gets \min_{r_v<r_{v'}}\{dp_{u,v'}+w_{v'}\}\\ dp_{u,v}&\gets \min_{l_{u'}<l_u}\{dp_{u',v}+w_{u'}\}\\ dp_{u,v}&\gets \min_{l_k<l_u,r_v<r_k}\{dp_{k,k}+w_k\} \end{aligned} $$即新购买的线段分别更新了左端点 / 右端点 / 同时更新 。
考虑优化第一个转移,注意到一个结论:若 ,且 不能被 购买,那么 一定不能被 购买,因为 ,且表示的区间都包含 ,那么 对应的区间 一定包含 对应的的区间 ,若 则 一定不属于 。
因此我们对于一个确定的 ,对 按 从大到小排序处理,用小根堆维护最小的 ,若当前的 无法到达则直接弹出,根据我们刚才的结论, 一定不可能更新 更小的状态。
对于第二种转移同理,我们对每个确定的 按 从小到大转移,分别维护小根堆即可。
考虑第三种转移: 的过程,考虑用前两种转移来描述:,但问题是状态 是不合法状态(),为了让这种转移可以被前两种转移刻画,我们定义这样的 为合法状态且值恰好是 即可。
按 从小到大枚举 ,按 从大到小枚举 ,维护 个小根堆分别处理 一定时的两种转移即可。
时间复杂度:。
代码呈现
#include<bits/stdc++.h> using namespace std; const int MAXN=2001,INF=2e9; int h[MAXN],L[MAXN],R[MAXN],P[MAXN],W[MAXN]; int dp[MAXN][MAXN]; //lanterns covered L[l]~R[r] int lb[MAXN][2],rb[MAXN][2]; //section >=L[i], section <=R[i] priority_queue <array<int,2>,vector<array<int,2>>,greater<array<int,2>>> Ql[MAXN],Qr[MAXN]; //best strategy when fix lp/rp signed main() { freopen("code.in","r",stdin); freopen("code.out","w",stdout); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d",&h[i]); for(int i=1;i<=m;++i) scanf("%d%d%d%d",&P[i],&W[i],&L[i],&R[i]); vector <int> ordL(m),ordR(m); iota(ordL.begin(),ordL.end(),1); sort(ordL.begin(),ordL.end(),[&](int x,int y){ return L[x]<L[y]; }); iota(ordR.begin(),ordR.end(),1); sort(ordR.begin(),ordR.end(),[&](int x,int y){ return R[x]>R[y]; }); for(int i=1;i<=m;++i) { for(int &p=lb[i][0]=P[i]+1;p>1&&h[p-1]>=L[i];--p); for(int &p=lb[i][1]=P[i]-1;p<n&&h[p+1]>=L[i];++p); for(int &p=rb[i][0]=P[i]+1;p>1&&h[p-1]<=R[i];--p); for(int &p=rb[i][1]=P[i]-1;p<n&&h[p+1]<=R[i];++p); } for(int i:ordL) for(int j:ordR) { dp[i][j]=INF; int l=max(lb[i][0],rb[j][0]),r=min(lb[i][1],rb[j][1]); if(P[i]<l||P[i]>r||P[j]<l||P[j]>r||(R[j]<R[i]&&L[i]>L[j])) continue; if(l==1&&r==n) dp[i][j]=0; else if(R[j]<R[i]) dp[i][j]=dp[i][i]; else if(L[i]>L[j]) dp[i][j]=dp[j][j]; else { auto gval=[&](auto &Q,int il,int ir,int hl,int hr) { while(!Q.empty()) { int idx=Q.top()[1]; if(P[idx]<il||P[idx]>ir||R[idx]<hl||L[idx]>hr) Q.pop(); else return Q.top()[0]; } return INF; }; dp[i][j]=min(dp[i][j],gval(Ql[i],l,r,L[i],R[j])); dp[i][j]=min(dp[i][j],gval(Qr[j],l,r,L[i],R[j])); } if(dp[i][j]<INF) Ql[i].push({dp[i][j]+W[j],j}),Qr[j].push({dp[i][j]+W[i],i}); } for(int i=1;i<=m;++i) { if(dp[i][i]<INF) printf("%d\n",dp[i][i]+W[i]); else puts("-1"); } return 0; }
- 1
信息
- ID
- 8711
- 时间
- 3000ms
- 内存
- 1024MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者