[ARC073F] Many Moves

[ARC073F] Many Moves

[ARC073F] Many Moves

题面翻译

在一行中有\(n\)个格子,从左往右编号为\(1\)\(n\)

\(2\)颗棋子,一开始分别位于位置\(A\)\(B\)。按顺序给出\(Q\)个要求,每个要求是如下形式:

  • 给出一个位置\(x_i\),要求将两个棋子中任意一个移动到位置\(x_i\)

将一颗棋子移动一格需要花费\(1\)秒,就是说将棋子从\(X\)位置移动到\(Y\)位置需要花费\(|X-Y|\)秒。

为了回答要求,你只能移动棋子,并且同一时刻只能移动一颗棋子。要求的顺序是不可更改的。在同一时间允许两颗棋子在同一个格子内。

输出格式

最小需要多少秒回答全部要求。

Solution:

我们设dp[x][y]表示当前两棋子的状态为(z,y)时的最小花费,显然只有当x,y,在\(x_i\)中出现过时状态才有意义

但是在N=2e5下,这个数组肯定是开不出来的
我们考虑再压一维:
循环到i时,必定有一个状态是x[i],另一个状态设为x[j]:

由于我们必须顺序转移,那么在dp[i][j]之前肯定已经算过dp[i-1][j]了,那么显然:

dp[i-1][j]->dp[i][j] 实际上就是将i-1移到了i,花费即为abs(x[i]-x[i-1]),此转移具有普遍性,也就是说,对于所有的点,如果进行i-1->i,他们的代价就是abs(x[i]-x[i-1]),这让我们很难不想到线段树区间加法

让我们把这个转移先放一边,然后来推一下别的转移:
dp[i-1][j]->dp[i-1][i],令pre<i<suf
则我们可以把转移的方程拆为:
dp[i-1][pre]-x[pre]+x[i]
dp[i-1][suf]+x[suf]-x[i]

显然,对于dp[i-1][i]这个状态来说,只要用线段树分别维护dp[i-1][pre]-x[pre],dp[i-1][suf]+x[suf]的最小值,然后直接查询后转移

前面说道到对于所有j<i-1的dp[i-1][j]->dp[i][j],都是可以通过线段树区间加法实现的,但是dp[i-1][i-1]->dp[i-1][i]却不能,所以我们还要加一个特殊的转移:
\(dp[i][i-1]=dp[i-1][i-1]+abs(x[i]-x[i-1])\)

所以现在明确了我们要用线段树维护:
\(dp[pos],dp[pos]-x[pos],dp[pos]+x[pos]\)
然后对于每一次答案统计,维护3个特殊转移:
\(dp[i][i-1]=\)
\(Min\) {
\(dp[i-1][i-1]+abs(x[i]-x[i-1)\)
\(dp[i-1][pre]-x[pre]+x[i]\)
\(dp[i-1][suf]+x[suf]-x[i]\)
}

剩下的直接暴力线段树区间加法

最后查询线段树根节点维护的dp值就好了

然后这题就愉快的做完了

(上述所有dp数组只是方便理解和反应状态,代码实现上甚至不用开出来),只需要在线段树上维护dp值就好了

Code:

#include<bits/stdc++.h>
#define int long long
const int N=2e5+5;
const int inf=1e17;
using namespace std;
int q[N];
int n,m,a,b;
//Segment_Tree
#define ls x<<1
#define rs x<<1|1
struct Tree{
    int l,r,val[3],tag;
}t[N<<2];
void push_up(int x)
{
    t[x].val[0]=min(t[ls].val[0],t[rs].val[0]);
    t[x].val[1]=min(t[ls].val[1],t[rs].val[1]);
    t[x].val[2]=min(t[ls].val[2],t[rs].val[2]);
}
void build(int x,int l,int r)
{
    t[x].l=l,t[x].r=r,t[x].val[0]=t[x].val[1]=t[x].val[2]=inf;
    if(l==r)
    {
        return;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
}
void add(Tree &T,int w)
{
    T.val[0]+=w;T.val[1]+=w;T.val[2]+=w;T.tag+=w;
}
void pushdown(int x)
{
    if(!t[x].tag)return ;
    add(t[ls],t[x].tag);
    add(t[rs],t[x].tag);
    t[x].tag=0;
}
void upd(int x,int pos,int w)
{
    if(t[x].l==t[x].r)
    {
        t[x].val[0]=w;
        t[x].val[1]=w-pos;
        t[x].val[2]=w+pos;
        return ;
    }
    int mid=t[x].l+t[x].r>>1;
    pushdown(x);
    if(pos<=mid)upd(ls,pos,w);
    if(mid<pos) upd(rs,pos,w);
    push_up(x);
}
void query(int x,int ll,int rr,int &res,int opt)
{
    if(rr<t[x].l||t[x].r<ll)return ;
    if(ll<=t[x].l&&t[x].r<=rr)
    {
        res=min(res,t[x].val[opt]);
        return ;
    }
    int mid=t[x].l+t[x].r>>1;
    pushdown(x);
    if(ll<=mid)query(ls,ll,rr,res,opt);
    if(mid<rr) query(rs,ll,rr,res,opt);
    push_up(x);
}
void work()
{
    cin>>n>>m>>a>>b;
    for(int i=1;i<=m;i++)
    {
        scanf("%lld",&q[i]);
    }
    build(1,1,n);
    upd(1,b,abs(q[1]-a));
    upd(1,a,abs(q[1]-b));
    for(int i=2,ans,ans0,ans1,ans2,len;i<=m;i++)
    {
        len=abs(q[i]-q[i-1]);
        ans=ans1=ans2=ans0=inf;
        query(1,q[i-1],q[i-1],ans0,0),  ans0+=len;
        query(1,1,q[i],ans1,1),     ans1+=q[i];
        query(1,q[i]+1, n  ,ans2,2),ans2-=q[i];
        add(t[1],len);
        ans=min(ans0,min(ans1,ans2));
        upd(1,q[i-1],ans);
    }
    printf("%lld\n",t[1].val[0]);
}
#undef int
int main()
{
    //freopen("ARC073F.in","r",stdin);
    //freopen("ARC073F.out","w",stdout);
    work();
    return 0;
}
posted @ 2024-12-06 11:55  liuboom  阅读(14)  评论(0)    收藏  举报