[ARC073F] Many Moves
[ARC073F] Many Moves
[ARC073F] Many Moves
题面翻译
在一行中有
有
- 给出一个位置
,要求将两个棋子中任意一个移动到位置 。
将一颗棋子移动一格需要花费
为了回答要求,你只能移动棋子,并且同一时刻只能移动一颗棋子。要求的顺序是不可更改的。在同一时间允许两颗棋子在同一个格子内。
输出格式
最小需要多少秒回答全部要求。
Solution:
我们设dp[x][y]表示当前两棋子的状态为(z,y)时的最小花费,显然只有当x,y,在
但是在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]却不能,所以我们还要加一个特殊的转移:
所以现在明确了我们要用线段树维护:
然后对于每一次答案统计,维护3个特殊转移:
}
剩下的直接暴力线段树区间加法
最后查询线段树根节点维护的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; }