zoukankan      html  css  js  c++  java
  • [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);
    }
    
  • 相关阅读:
    个人工作总结07
    uboot是用来干什么的,有什么作用?
    交叉编译器的命名规则及详细解释(arm/gnu/none/linux/eabi/eabihf/gcc/g++)
    C++中explicit关键字的使用
    ubuntu 虚拟机下使用摄像头
    用CMAKE编译OpenCV 3.4.2+Opencv Contrib 3.4生成可执行包
    人脸识别:objectDetection
    opencv 3 -- waitKey()函数
    CreateThread与_beginthreadex本质区别
    lib文件和dll文件
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14782799.html
Copyright © 2011-2022 走看看