[NOIP 2018 Plus] 奇怪的题(贪心 + DP) | 错题本

24 篇文章 1 订阅
8 篇文章 0 订阅

题目

(原题来自牛客多校赛)
题目描述
我们称一个长度为 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=ippi=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=1n2aiapi+aiaqi}

输入格式
第一行一个正整数 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

inout
4
0 8 0 0
16

样例 #2

inout
6
3 1 4 1 5 9
16

数据规模与约定
对于 100 % 100\% 100% 的数据, 4 ≤ n ≤ 1 0 6 4\le n\le10^6 4n106 a i ≤ 1 0 9 a_i\le10^9 ai109,保证 n n n 是偶数。
对于不同的测试点,作如下约定:

测试数据编号 n ≤ n\le n
1 ∼ 2 1\sim2 12 4 4 4
3 ∼ 4 3\sim4 34 100 100 100
5 ∼ 7 5\sim7 57 1000 1000 1000
8 ∼ 10 8\sim10 810 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 (k4) 的大段,实际上一定可以分成 x   ( x ≥ 0 ) x \ (x \geq 0) x (x0) 个长为 4 4 4 的大段和 y   ( y ≥ 0 ) y \ (y \geq 0) y (y0) 个长为 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值