2020 CSP-J 多校赛 T2

排序 (sort)

题目限制

  • 内存限制:512MB
  • 时间限制:1000ms
  • 文件输入输出
    • 输入文件:sort.in
    • 输出文件:sort.out

题目知识点

  • 贪心

题目来源

2020 CSP-J 多校赛 T2


题目

题目背景

14 14 14 学完了排序,有感而发,又想出了一个比较妙的题目 (但是他又不会做,QAQ)

题目描述

14 14 14 有一个长度为 n n n 的序列 a a a里面的元素互不相同 14 14 14 要对它从小到大排序
14 14 14 每次可以选择两个不相同的位置 i , j i, j i,j,交换这两个位置上的元素的代价为 a i + a j a_i + a_j ai+aj
14 14 14 想知道将这个序列变成递增序列至少需要多少代价

格式

输入格式 (sort.in)

输入第一行包含一个整数 n n n,表示序列的长度
输入第二行包含 n n n 个整数,表示序列 a a a

输出格式 (sort.out)

输出只有一行,表示花费的最少代价

样例

样例1

样例输入
3
2 3 1
样例输出
7
样例解释

先交换 a 2 a_2 a2 a 3 a_3 a3,代价为 1 + 3 = 4 1 + 3 = 4 1+3=4,序列变为 2 1 3;再交换 a 1 a_1 a1 a 2 a_2 a2,代价为 2 + 1 = 3 2 + 1 = 3 2+1=3,序列变为 1 2 3
所以总代价为 7 7 7,可以证明这是最小的代价

样例2

样例输入
4
2 3 6 1
样例输出
14

提示

数据范围

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109


思路

我们可以考虑对 序列 a a a 从小到大排序得到 序列 b b b,对于每一个 a i a_i ai 我们进行连边,将 a i a_i ai 向 它 在 序列 b b b 出现的位置连一条有向边(因为元素互不相同,所以得到的元素唯一)
建完边之后,可以发现该图只由若干个环组成,我们的目标就是通过交换使图变成 n n n 个自环


分析

考虑贪心,对于一个环,我们有两种方案:
1. 1. 1. 选出这个环中的最小值,按照这个边进行交换

  • 环内所有元素 的和为 c n t cnt cnt,最小值为 n u m num num,交换 t o t tot tot
  • 对于 t o t tot tot 次交换,方案的结果就是 ∑ \sum 每个元素 × \times × 每个元素交换的次数
  • 除了 最小值的元素 只会交换 1 1 1 次,最小值会交换 t o t tot tot 次,由于 c n t cnt cnt 中累加了 1 1 1 n u m num num;所以最终结果为:(cnt - num) + (tot × \times × num) ,即 cnt + num × \times × (tot - 1)

2. 2. 2. 把这个环外面的最小值拉进来,和环内的元素交换

  • 环内所有元素 的和为 c n t cnt cnt环外最小值 M i n A MinA MinA,在环内交换 t o t tot tot
  • 对于 t o t tot tot 次交换,方案的结果就是 ∑ \sum 每个元素 × \times × 每个元素交换的次数
  • 环内的最小值 会交换 2 2 2 次 (第 1 1 1 次 是把 环外最小值 引入环内,把这个数引出环内;第 2 2 2 次 是把这个数引入环内)
  • 环内其余的元素 只会交换 1 1 1
  • 环外的最小值 在环内交换了 t o t tot tot 次 (其中没有与 环内最小值 交换),与 环内的最小值 交换了 2 2 2 次,所以它交换了 t o t + 2 tot + 2 tot+2
  • 所以最终的答案为:cnt - num + num × \times × 2 + MinA × \times × (tot + 2),即 cnt + num + MinA × \times × (tot + 2)

把每个环的两个方案的最小值加起来,就是最后的答案了


代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int INF = 2e9;
const int MAXN = 1e6;
int N;
int A[MAXN + 5];
long long ans = 0LL;
int lsh[MAXN + 5];
int MinA = INF, Pos[MAXN + 5];
bool vis[MAXN + 5];

long long Min(long long a, long long b) { return a < b ? a : b; }

void Read(int &n)
{
	n = 0;
	bool f = 1;
	char C = getchar();
	while (C < '0' || C > '9')
	{
		if (C == '-') f ^= 1;
		C = getchar();
	}
	while ('0' <= C && C <= '9')
	{
		n = (n << 3) + (n << 1) + (C ^ 48);
		C = getchar();
	}
	if (!f) n = 0;
}

int main()
{
	freopen("sort.in", "r", stdin);
	freopen("sort.out", "w", stdout);
	Read(N);
	for (int i = 1; i <= N; i++)
	{
		Read(A[i]); lsh[i] = A[i];
		MinA = Min(MinA, A[i]);
	}
	sort(lsh + 1, lsh + N + 1);
	for (int i = 1; i <= N; i++)
		Pos[i] = lower_bound(lsh + 1, lsh + N + 1, A[i]) - lsh;
	for (int i = 1; i <= N; i++)
	{
		if (!vis[i])
		{
			vis[i] = 1;
			long long cnt = 1LL * lsh[Pos[i]];
			int num = INF, tot = 0;
			for (int j = Pos[i]; j != i; j = Pos[j]) {
				vis[j] = 1, ++tot;
				cnt += 1LL * lsh[Pos[j]];
				num = Min(num, lsh[Pos[j]]);
			}
			if (tot > 0) ans += Min(cnt + 1LL * num * (tot - 1), cnt + 1LL * num + 1LL * MinA * (tot + 2));
		}
	}
	printf("%lld\n", ans);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值