一、题目
有 (n) 个人,编号从 (1) 到 (n),一开始位置 (i) 上站着人 (p_i),有这三种操作,问将人从小到大排序的最小花费:
- 支付 (a_i),可以让第 (i) 个人移动到任意的位置。
- 支付 (b_i),可以让第 (i) 个人移动到最左边的位置。
- 支付 (c_i),可以让第 (i) 个人移动到最右边的位置。
(1leq nleq 2cdot 10^5,1leq p_ileq n,1leq a_i,b_i,c_ileq 10^9)
二、解法
最后是把所有人从小到大排,可以思考怎么样把每个人换到该换的地方去。显然每个人最多换一次,那么每个人就有四种状态:不换;换任意位置;换到最左边位置;换到最右边位置。
这就是一个确定状态的问题,我们先考虑暴力,再考虑优化。要想一想有没有比枚举每个点的四种状态更好的做法,我们可以枚举集合 (S=(s_1,s_2...s_k)),(s_i) 表示 (s_i) 这个人不会动,那么对于剩下的人能很自然地确定他们的状态:
- (1leq x<s_1),这些人就支付 (min(a_x,b_x)) 换到最终的位置。
- (s_i< x<s_{i+1}),这些人就支付 (a_x) 换到最终的位置(使用 (2,3) 操作一定会越过不能动的点)
- (s_{k}<xleq n),这些人就支付 (min(a_x,c_x)) 换到最终的位置。
不难发现 (s_1<s_2<...<s_k),这变成了在 ([1,n]) 这个线段上的规划问题,设 (dp[i]) 为将第 (i) 个人规定不动的最小花费,那么我们枚举上一个规定不动的人 (j) 或者直接把他作为第一个人:
[dp[i]=min(sum_{k=1}^{i-1}min(a_k,b_k),dp[j]+sum_{k=i+1}^{j-1}a_k)
]
最后的答案是 (min dp[i]+sum_{k=i+1}^nmin(a_k,c_k)),全部人都换的情况一定不是最优的。
显然我们可以用线段树优化做到 (O(nlog n))
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 200005;
const int inf = 1e18;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,ans,p[M],a[M],b[M],c[M],A[M],B[M],C[M],dp[M],bit[M];
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
bit[i]=min(bit[i],y);
}
int ask(int x)
{
int r=inf;
for(int i=x;i>0;i-=lowbit(i))
r=min(r,bit[i]);
return r;
}
signed main()
{
n=read();ans=inf;
for(int i=1;i<=n;i++)
{
int x=read();
p[x]=i;//the position of person x
bit[i]=inf;
}
for(int i=1;i<=n;i++)
{
a[i]=read(),b[i]=read(),c[i]=read();
A[i]=A[i-1]+a[i];
B[i]=B[i-1]+min(a[i],b[i]);
C[i]=C[i-1]+min(a[i],c[i]);
}
for(int i=1;i<=n;i++)
{
dp[i]=min(B[i-1],ask(p[i])+A[i-1]);
add(p[i],dp[i]-A[i]);
ans=min(ans,dp[i]+C[n]-C[i]);
}
printf("%lld
",ans);
}