【arc073D】Many Moves
Description
有\(n\)个格子,编号从左到右为\(1\sim n\),一开始有两个棋子,位置给定,接下来要依次进行\(Q\)次操作,第\(i\)次操作必须选择一个棋子将其移动到\(x_i\)上面(\(x\)数组给定),所需代价是当前位置与目标位置的编号之差绝对值,允许两个棋子在同一个位置,求完成操作的最小代价
Solution
大家好我是一个不会算复杂度的弱智选手
这题的话。。没有什么想法那就快乐dp咯。。但是状态的设置比较重要,注意到两个棋子其实本质上没有任何区别,我们希望用一种简明的方式将两个棋子的位置都记录下来,然后再注意到第\(i\)次操作完后一定有一个棋子在\(x_i\),那么我们可以用\(f[i][j]\)表示第\(i\)次操作完之后,不在\(x_i\)位置上的那个棋子在\(j\)这个位置,所用的最小代价
那么可以写出转移:
其中\(k\)的枚举范围是\(1\sim n\)
现在最大的问题就是。。第二种情况,然而注意到比较开心的事情是这种情况只会在一个特定的位置出现所以我们可以考虑用某些数据结构得到这个\(min(f[i-1][k]+|k-x_i|)\)
那当然是用线段树啊,但是这个绝对值有点恶心,但是由于\(x_i\)已知,所以我们可以直接暴力处理一下,对于\(k\in[1,x_i]\)我们询问\(min(f[i-1][k]-k)\),对于\(k\in [x_i,n]\)我们询问\(min(f[i-1][k]+k)\),然后一个\(+x_i\)一个\(-x_i\)再取一下\(min\)就可以了(也就是说线段树分别维护\(f[i]\)、\(f[i]-i\)和\(f[i]+i\)的区间最小值)
至于\(f[i-1][j]+|x_i-x_{i-1}|\)的情况,我们直接区间打标记就好了
注意一下需要开long long
mark:不要看到三个变量就觉得。。优化之后是\(n^2logn\)的。。。
mark:遇到绝对值考虑暴力分两段,如果计算方式不同的话记得分界点两边的都要算
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=200010,inf=2147483647;
int a[N];
namespace Seg{/*{{{*/
const int N=::N*4;
int ch[N][2];
ll mn[N][3],tag[N];//order: - none +
int n,tot;
void _build(int x,int l,int r){
for (int i=0;i<3;++i) mn[x][i]=inf;
if (l==r) return;
int mid=l+r>>1;
ch[x][0]=++tot; _build(ch[x][0],l,mid);
ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
}
void build(int _n){n=_n; tot=1; _build(1,1,n);}
void pushup(int x){
for (int i=0;i<3;++i) mn[x][i]=min(mn[ch[x][0]][i],mn[ch[x][1]][i]);
}
void givetag(int x,ll delta){
tag[x]+=delta;
for (int i=0;i<3;++i) mn[x][i]+=delta;
}
void downtag(int x){
if (!tag[x]) return;
if (ch[x][0]) givetag(ch[x][0],tag[x]);
if (ch[x][1]) givetag(ch[x][1],tag[x]);
tag[x]=0;
}
void _update(int x,int d,int lx,int rx,ll delta){
if (lx==rx){
mn[x][0]=min(mn[x][0],delta-lx);
mn[x][1]=min(mn[x][1],delta);
mn[x][2]=min(mn[x][2],delta+lx);
return;
}
downtag(x);
int mid=lx+rx>>1;
if (d<=mid) _update(ch[x][0],d,lx,mid,delta);
else _update(ch[x][1],d,mid+1,rx,delta);
pushup(x);
}
void update(int d,ll delta){_update(1,d,1,n,delta);}
ll _query(int x,int l,int r,int lx,int rx,int op){
if (l<=lx&&rx<=r) return mn[x][op];
int mid=lx+rx>>1;
downtag(x);
if (r<=mid) return _query(ch[x][0],l,r,lx,mid,op);
else if (l>mid) return _query(ch[x][1],l,r,mid+1,rx,op);
else{
return min(_query(ch[x][0],l,mid,lx,mid,op),_query(ch[x][1],mid+1,r,mid+1,rx,op));
}
pushup(x);
}
ll query(int l,int r,int op){return _query(1,l,r,1,n,op);}
}/*}}}*/
int n,m,st1,st2;
int Abs(int x){return x<0?-x:x;}
void debug(int op){
for (int i=1;i<=n;++i) printf("%lld ",Seg::query(i,i,op));
printf("\n");
}
void dp(){
ll tmp;
Seg::update(st2,0);
//debug(2);
for (int i=1;i<=m;++i){
tmp=min(Seg::query(1,a[i],0)+a[i],Seg::query(a[i],n,2)-a[i]);
Seg::givetag(1,Abs(a[i]-a[i-1]));
Seg::update(a[i-1],tmp);
//debug(1);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d%d%d%d",&n,&m,&st1,&st2);
for (int i=1;i<=m;++i) scanf("%d",a+i);
a[0]=st1;
Seg::build(n);
dp();
printf("%lld\n",Seg::query(1,n,1));
}