[ARC073F] Many Moves

[ARC073F] Many Moves

[ARC073F] Many Moves

题面翻译

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

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

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

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

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

输出格式

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

Solution:

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

但是在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][i1]=dp[i1][i1]+abs(x[i]x[i1])

所以现在明确了我们要用线段树维护:
dp[pos],dp[pos]x[pos],dp[pos]+x[pos]
然后对于每一次答案统计,维护3个特殊转移:
dp[i][i1]=
Min {
dp[i1][i1]+abs(x[i]x[i1)
dp[i1][pre]x[pre]+x[i]
dp[i1][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 @   liuboom  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示