CF1566F Points Movement 题解
forest114514 · · 题解
CF1566F Points Movement
老套路了,线段只有相交没有包含,包含的只需满足较小的那条就行了,这样线段的端点就单增。
当然,现在已经有点被包含的线段也不用算了!
这样就只剩一堆单调的线段和一堆点交替出现,我们加两个虚点坐标为
我们可以花费
一个点的走法有四种:
- 只向左走
- 只向右走
- 向左走一段,原路返回后向右走
- 向右有一段,原路返回后向左走
但其实只用考虑情况 3,4 因为情况 1,2 等价于向反方向走了长度为
更一般,这两种情况其实可以一起取
如果我们记
考虑每个点的移动一定在上一个点与这一个点的范围内,所以我们只考虑它一开始向左/右走,同时我们采用贡献转移的思路,将往右走的贡献”送“给右端的点。
记
答案为
时间复杂度为
CODE:
struct Line{
int l,r;
};
void solve(){
int n,m;
read(n,m);
LL a[n+10];LL f[n+10][2];
rep(i,1,n) read(a[i]);
a[0]=-inf,a[n+1]=inf;
sort(a,a+2+n);
vector<Line> b; int l,r;
repp(i,0,m){
read(l,r);
if(lower_bound(a,a+2+n,l)==upper_bound(a,a+2+n,r)) b.pb(Line{l,r});
}
auto cmp=[](Line x,Line y){
return (x.r^y.r)?x.r<y.r:x.l>y.l;
};
sort(b.begin(),b.end(),cmp);
repp(i,1,b.size()){
if(b[i].l<=b[i-1].l||b[i].r==b[i-1].r){
b.erase(b.begin()+i);
--i;
}
}
rep(i,1,n+1) f[i][0]=f[i][1]=INF;
f[0][0]=f[0][1]=0;
int now=0;
auto min=[](LL a,LL b){
return (a<b)?a:b;
}; vector<LL> cl,cr;
rep(i,1,n+1){
cl.pb(a[i-1]);
while(now<b.size()&&b[now].r<a[i]){
cr.pb(b[now].r);
cl.pb(b[now].l);
++now;
}
cr.pb(a[i]);
repp(j,0,cl.size()){
LL disl=cl[j]-a[i-1],disr=a[i]-cr[j];
f[i][0]=min(f[i][0],f[i-1][0]+disl+2*disr);
f[i][0]=min(f[i][0],f[i-1][1]+2*disl+2*disr);
f[i][1]=min(f[i][1],f[i-1][0]+disl+disr );
f[i][1]=min(f[i][1],f[i-1][1]+2*disl+disr);
}
cl.clear(),cr.clear();
}
write(min(f[n+1][1],f[n+1][0]),'\n');
}