排序 (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 1≤n≤106, 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109
思路
我们可以考虑对 序列
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;
}