【arc073D】Many Moves

Portal -->arc073D

Description

​  有\(n\)个格子,编号从左到右为\(1\sim n\),一开始有两个棋子,位置给定,接下来要依次进行\(Q\)次操作,第\(i\)次操作必须选择一个棋子将其移动到\(x_i\)上面(\(x\)数组给定),所需代价是当前位置与目标位置的编号之差绝对值,允许两个棋子在同一个位置,求完成操作的最小代价

​  

Solution

​  大家好我是一个不会算复杂度的弱智选手

​  这题的话。。没有什么想法那就快乐dp咯。。但是状态的设置比较重要,注意到两个棋子其实本质上没有任何区别,我们希望用一种简明的方式将两个棋子的位置都记录下来,然后再注意到第\(i\)次操作完后一定有一个棋子在\(x_i\),那么我们可以用\(f[i][j]\)表示第\(i\)次操作完之后,不在\(x_i\)位置上的那个棋子在\(j\)这个位置,所用的最小代价

​​  那么可以写出转移:
\[ f[i][j]=\begin{cases} f[i-1][j]+|x_i-x_{i-1}|&(j\neq x_{i-1})\\ \\ min(f[i-1][j]+|x_i-x_{i-1}|,min(f[i-1][k]+|k-x_i|))&(j=x_{i-1})\\ \end{cases} \]
​  其中\(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));
}

转载于:https://www.cnblogs.com/yoyoball/p/9825524.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值