题目
(原题来自牛客多校赛)
题目描述
我们称一个长度为
n
n
n 的排列
{
p
n
}
\{p_n\}
{pn} 为“match”,当且仅当对于任意
i
i
i,
p
i
≠
i
∧
p
p
i
=
i
p_i\not=i\land p_{p_i}=i
pi=i∧ppi=i;称两个“match“不同,当且仅当对于任意
i
i
i,
p
i
≠
q
i
p_i\not=q_i
pi=qi。
设
p
,
q
p,q
p,q 是两个不同的”match“,给定序列
{
a
n
}
\{a_n\}
{an},求 :
min
{
∑
i
=
1
n
∣
a
i
−
a
p
i
∣
+
∣
a
i
−
a
q
i
∣
2
}
\min\left\{\sum_{i=1}^n\frac{|a_i-a_{p_i}|+|a_i-a_{q_i}|}2\right\}
min{i=1∑n2∣ai−api∣+∣ai−aqi∣}
输入格式
第一行一个正整数
n
n
n,表示序列长度。
第二行
n
n
n 个非负整数
a
1
,
a
2
,
⋯
,
a
n
a_1,a_2,\cdots,a_n
a1,a2,⋯,an,描述序列
{
a
n
}
\{a_n\}
{an}。
输出格式
输出一行,表示式子的答案。
输入输出样例
样例 #1
in | out |
---|---|
4 0 8 0 0 | 16 |
样例 #2
in | out |
---|---|
6 3 1 4 1 5 9 | 16 |
数据规模与约定
对于
100
%
100\%
100% 的数据,
4
≤
n
≤
1
0
6
4\le n\le10^6
4≤n≤106,
a
i
≤
1
0
9
a_i\le10^9
ai≤109,保证
n
n
n 是偶数。
对于不同的测试点,作如下约定:
测试数据编号 | n ≤ n\le n≤ |
---|---|
1 ∼ 2 1\sim2 1∼2 | 4 4 4 |
3 ∼ 4 3\sim4 3∼4 | 100 100 100 |
5 ∼ 7 5\sim7 5∼7 | 1000 1000 1000 |
8 ∼ 10 8\sim10 8∼10 | 1 0 6 10^6 106 |
分析
贪心果然还是最难的 = =
理性分析一波得到问题就是将数组中的数两两匹配。如果把匹配后各对数的差值之和记为这种匹配方式的权值,问题要求找到两种 完全不同 的匹配方式,是他们的权值之和最小。
可以发现其中一种方式肯定是排序过后相邻的两两匹配。
另外一种方式同样先排序,然后参照方式一我们可以这样匹配(最优性不太会严谨证明……):
其中
A
A
A 表示该数贡献为正,
B
B
B 表示该数贡献为负。有了这个思路可以得到一个
O
(
n
2
)
O(n^2)
O(n2) 的 DP。然后实际上用优先队列一类的东西就足以通过本题。但是继续分析,将上图中的“一大段”单独取出,发现显然这“一大段”越长代价越大,因为除了相邻两项差值的贡献,对答案造成实质影响的其实是这一大段的右端点和左端点的差值。对于一个长为
2
k
(
k
≥
4
)
2k \ (k \geq 4)
2k (k≥4) 的大段,实际上一定可以分成
x
(
x
≥
0
)
x \ (x \geq 0)
x (x≥0) 个长为
4
4
4 的大段和
y
(
y
≥
0
)
y \ (y \geq 0)
y (y≥0) 个长为
6
6
6 的大段,因为
4
x
+
6
y
=
2
(
2
x
+
3
y
)
4x + 6y = 2(2x + 3y)
4x+6y=2(2x+3y),又
(
2
,
3
)
=
1
(2, 3) = 1
(2,3)=1 即
2
x
+
3
y
2x + 3y
2x+3y 可以表示一切正整数。这样一来,我们只需要考虑长度为
4
4
4 的转移和长度为
6
6
6 的转移!
错因
- 没有想到只需要长为 4 4 4 和长为 6 6 6 的段转移。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
typedef long long LL;
const int MAXN = 1000000;
const LL INF = 1ll << 60;
int N;
int A[MAXN + 5];
LL Dp[MAXN + 5];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++)
scanf("%d", &A[i]);
std::sort(A + 1, A + N + 1);
LL Ans = 0;
for (int i = 1; i <= N; i += 2)
Ans += A[i + 1] - A[i];
Dp[2] = INF;
for (int i = 4; i <= N; i += 2)
Dp[i] = std::min(Dp[i - 4] + A[i] - A[i - 3] + A[i - 1] - A[i - 2],
(i >= 6) ? (Dp[i - 6] + A[i] - A[i - 5] + A[i - 1] - A[i - 2] + A[i - 3] - A[i - 4]) : INF);
printf("%lld", Ans + Dp[N]);
return 0;
}