[ABC201F] Insertion Sort

一、题目

点此看题

(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);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14782799.html