[ABC201F] Insertion Sort
一、题目
有 \(n\) 个人,编号从 \(1\) 到 \(n\),一开始位置 \(i\) 上站着人 \(p_i\),有这三种操作,问将人从小到大排序的最小花费:
- 支付 \(a_i\),可以让第 \(i\) 个人移动到任意的位置。
- 支付 \(b_i\),可以让第 \(i\) 个人移动到最左边的位置。
- 支付 \(c_i\),可以让第 \(i\) 个人移动到最右边的位置。
\(1\leq n\leq 2\cdot 10^5,1\leq p_i\leq n,1\leq a_i,b_i,c_i\leq 10^9\)
二、解法
最后是把所有人从小到大排,可以思考怎么样把每个人换到该换的地方去。显然每个人最多换一次,那么每个人就有四种状态:不换;换任意位置;换到最左边位置;换到最右边位置。
这就是一个确定状态的问题,我们先考虑暴力,再考虑优化。要想一想有没有比枚举每个点的四种状态更好的做法,我们可以枚举集合 \(S=(s_1,s_2...s_k)\),\(s_i\) 表示 \(s_i\) 这个人不会动,那么对于剩下的人能很自然地确定他们的状态:
- \(1\leq x<s_1\),这些人就支付 \(\min(a_x,b_x)\) 换到最终的位置。
- \(s_i< x<s_{i+1}\),这些人就支付 \(a_x\) 换到最终的位置(使用 \(2,3\) 操作一定会越过不能动的点)
- \(s_{k}<x\leq 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}^n\min(a_k,c_k)\),全部人都换的情况一定不是最优的。
显然我们可以用线段树优化做到 \(O(n\log 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\n",ans);
}