ARC 073 F Many Moves - 线段树 - dp

题目大意:
有一条数轴,两个人初始在a,b。每次给个x,你要让其中恰好一个人移动到这个x,代价是距离。求最小代价之和。
题解:
场上一直在想贪心还过了样例……
冷静一下考虑一个朴素dp,设 f [ i ] [ j ] f[i][j] f[i][j]表示前i次询问,另一个人在x_j的情况。
那么发现这么一件事情:如果 j ̸ = i − 1 j\not=i-1 j̸=i1,那么总是让一个人从i-1走到i的。所以这个状态直接由 f [ j + 1 ] [ j ] f[j+1][j] f[j+1][j]决定。
因此令 f [ i ] f[i] f[i]表示一个人在i另一个在i-1的答案。那么最终答案就是 f [ i ] + S ( i , q ) f[i]+S(i,q) f[i]+S(i,q)取min。
显然有 f [ i ] = min ⁡ j = 1 i − 1 f j + S ( j , i − 1 ) + ∣ x i − x j − 1 ∣ f[i]=\min_{j=1}^{i-1}f_j+S(j,i-1)+|x_i-x_{j-1}| f[i]=minj=1i1fj+S(j,i1)+xixj1,后面那个绝对值拆开然后线段树优化一下即可。

#include<bits/stdc++.h>
#define gc getchar()
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define INF (LLONG_MAX/2-10)
#define db double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
const int N=200010;lint s[N],f[N];int x[N];
inline int gabs(int x) { return x<0?-x:x; }
struct segment{
    int l,r;lint v[2];
    segment *ch[2];
}*rt;
int build(segment* &rt,int l,int r)
{
    rt=new segment,rt->l=l,rt->r=r;int mid=(l+r)>>1;
    rt->v[0]=rt->v[1]=INF;if(l==r) return 0;
    return build(rt->ch[0],l,mid),build(rt->ch[1],mid+1,r);
}
inline int push_up(segment* &rt)
{
    rt->v[0]=min(rt->ch[0]->v[0],rt->ch[1]->v[0]);
    rt->v[1]=min(rt->ch[0]->v[1],rt->ch[1]->v[1]);
    return 0;
}
int update(segment* &rt,int p,lint fs)
{
    int l=rt->l,r=rt->r,mid=(l+r)>>1;
    if(l==r) return rt->v[0]=min(rt->v[0],fs-p),rt->v[1]=min(rt->v[1],fs+p),0;
    if(p<=mid) update(rt->ch[0],p,fs);else update(rt->ch[1],p,fs);
    return push_up(rt);
}
lint query(segment* &rt,int s,int t,int k)
{
    int l=rt->l,r=rt->r,mid=(l+r)>>1;lint ans=INF;
    if(s<=l&&r<=t) return rt->v[k];
    if(s<=mid) ans=min(ans,query(rt->ch[0],s,t,k));
    if(mid<t) ans=min(ans,query(rt->ch[1],s,t,k));
    return ans;
}
inline lint solve(int n,int q)
{
    rep(i,1,q) s[i]=s[i-1]+gabs(x[i]-x[i-1]);
    f[1]=0,build(rt,1,n),update(rt,x[0],f[1]-s[1]);
    lint ans=f[1]+s[q]-s[1];
    for(int i=2;i<=q;i++)
    {
        f[i]=min(query(rt,1,x[i],0)+x[i],query(rt,x[i],n,1)-x[i])+s[i-1];
        update(rt,x[i-1],f[i]-s[i]),ans=min(ans,f[i]+s[q]-s[i]);
    }
    return ans;
}
int main()
{
    int n=inn(),q=inn()+1;x[0]=inn(),x[1]=inn();
    rep(i,2,q) x[i]=inn();lint ans=solve(n,q);
    swap(x[0],x[1]),ans=min(ans,solve(n,q));
    return !printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值