P1220 关路灯 题解
题解来历
这题其实是有
\mathcal{O}(n) 做法的。 —— grass8cow
upd on 25.7.25 感谢 KobeBeanBryantCox 的指正,非常抱歉在题解中出现如此严重的错误,以及给题解审核员带来了不必要的麻烦。
前置知识
斜率优化,单调队列。
分析
发现操作序列一定是向左走若干步,调头向右,再向左……的形式。
如果每个点我们都用最快的时间到达,那么答案即为:
显然,这个条件是无法满足的,每一次向左或向右移动,答案都会增加,因此,我们设
假设我们有
记
拆开之后,显然是一个斜率优化的式子。从
实现的时候,我们可以先令
以上做法是原题解,可惜它是错误的。
为什么呢,发现这样子做,对于
比如这样的数据:
5 4
1 1000
2 1000
3 2
4 1
5 100
仅凭感性分析,我们得知它的最优策略是 4 3 2 1 5,但是代码却会给出 4 5 3 2 1 的答案。
因此,我们在 dp 时需要同时保证:对于
怎么办?对于
得到
发现,我们将本该计算在
发现
如果你足够厉害,应该是能直接设出
代码
#include "iostream"
#include "cstdio"
#include "cmath"
using namespace std;
const int maxn=55+6;
int w[maxn],dis[maxn],dp[maxn];
struct Node { int x,y;};
inline double calc(Node a,Node b) {return 1.0*(a.y-b.y)/(a.x-b.x);}
struct Deque {
Node q[maxn]; int l,r;
Deque() {l=1,r=0;}
inline void ins(int u,int v) {
// cout<<"ins:"<<u<<" "<<v<<'\n';
Node t={u,v};
while(l<r&&calc(q[r-1],q[r])>=calc(q[r-1],t)) --r;
q[++r]=t;
}
inline int ask(int u) {
while(l<r&&u>calc(q[l],q[l+1])) ++l;
// cout<<"qry:"<<u<<" "<<q[l].y<<" "<<q[l].x<<'\n';
return q[l].y-q[l].x*u;
}
}L[2];
int main() {
int n,c; cin>>n>>c;
int ans=0;
for(int i=1;i<=n;++i) cin>>dis[i]>>w[i];
for(int i=1;i<=n;++i) {
ans+=abs(dis[c]-dis[i])*w[i];
w[i]+=w[i-1]; // cout<<ans<<'\n';
}
int l=c,r=c;
L[1].ins(-w[l-1],-2*dis[c]*w[c-1]); L[0].ins(w[c]-w[n],2*dis[c]*(w[n]-w[c]));
while(1!=l&&r!=n) {
int qr=L[1].ask(2*dis[r+1]),ql=L[0].ask(-2*dis[l-1]);
if(ql<qr) dp[--l]=ql,L[1].ins(-w[l-1],dp[l]-2*dis[l]*w[l-1]);
else dp[++r]=qr,L[0].ins(w[r]-w[n],dp[r]+2*dis[r]*(w[n]-w[r]));
// cout<<l<<" "<<r<<" "<<ql<<" "<<qr<<" "<<dp[l]<<" "<<dp[r]<<'\n';
}
if(l==1) ans+=dp[1];
else ans+=dp[n];
cout<<ans;
return 0;
}
/*
5 3
2 10
3 20
5 20
6 30
8 10
*/