E-Two Matchings

比赛期间写博文,队友我家挖祖坟
数论只会g c d,队友AC我挂机

题目连接

注意本文中的部分字母和原文稍有不同,请注意!

题意

定义序列 ,满足如下要求

  • 长度为 的序列 组成

定义一个字符串的费用为 为给出的权值数组

求两个满足上述对序列 的描述的序列 ,同时还要满足 对于每一个 都成立

则这两个序列的费用和的最小值是多少

分析

根据条件

  • 长度为 的序列 组成

可以得到序列是由基础序列 通过进行两两对调得到,且每个值进行且只进行一次对调。(这里就不仔细证明了,应该……在打这个比赛的人应该都能理解吧)

而我们需要得到的就是两个费用最小的串,即最小串和次小串

注意,接下来的讨论仅讨论排序后的下标,即如果写着 则指代 sort 后的数组 中最小的值

最小值

首先是最小的值,那很明显,把 w 数组排序后,间隔着相减就可以得到,例如下面已经排序后的下标序列:

我们可以得到其最小的解为

我们暂时不去处理这个解,保留原样

次小值

接下来是次优解,首先应当保证其每一位的值不相同

由于我们已经将最小值的组合取完了,则次优解就有了非常多的限制

我们可以“旋转”这个数列得到

把这个“旋转”暂时称为 ,指代 个元素的旋转

而此时即为次优的解。

证明

我们以六个数字的数列来证明上述操作

首先用 表示这个值作为其所在的交换中的较小值, 表示这值作为其所在的交换中的较大值

例如最小值可以表示为

下标 1 2 3 4 5 6
最小值 - + - + - +

我们并不需要具体考虑哪个值与哪个值交换,因为最终的求和结果是一样的,即上面的值与下面的符号结合后相加就是最终结果。

除去最小解后,我们只有以下两种组合方法

下标 1 2 3 4 5 6
最小值 - + - + - +
方案1 - - + - + +
方案2 - - - + + +
错误方案 - - + + - +

这里举例一个错误的方案,虽然看起来此方案是与最小值方案不同,但是注意一下最后两个值,无论这个错误方案怎么组合, 必然要发生组合并发生交换,则与最小值的方案出现重复,则不行。

那么我们比较一下这两个方案哪个更优

(使用分数线仅用于视觉上更好的体现上下的对比效果,并无除法运算思想,下同)

注意,这里不能取 因为在配对的时候我们已经保证了右边的加号匹配左边的减号,即必定为正数

很明显,方案1更优,即上方的次优解

(感谢 @yyymmmi@hnust_zhangpeng 指出错误,现已更正)

合并最小值和次小值

我们将两个解相加发现最终结果为

长度不及 的时候

而对于长度仅为 的串,只能 ,即

此时的最终结果为(过程忽略)

长度为的时候

那么我们再往长度增长的方向考虑,当 时,我们有两个方案,

  1. 两个 )来旋转它
  2. 两个 )来旋转它
  3. 一个 来旋转它

注意,此题是不存在 的,因为这毫无意义,所以 时,没有一个 和一个 这样的组合。

先比较一下两个

我们选择使用方案

接下来是方案 和方案 的比较

此时证明得到方案 在三个方案内最优,此时 时的答案为:

同时我们得到了一个结论:仅存在 两种旋转,如果存在 则可以将此 分解为两个 可以更优。

长度更长的时候

时,即可以将整个串分解成多个 和多个 组成。

那么得到了 的递推公式:dp[i] = min(dp[i - 4] + v[i - 1] - v[i - 4], dp[i - 6] + v[i - 1] - v[i - 6])

注意 的初始值有三个:

AC code

#include <bits/stdc++.h>

using namespace std;

long long dp[200100];

void solve() {
    int T;
    cin >> T;
    for (int ts = 0; ts < T; ++ts) {
        int n;
        cin >> n;
        vector<long long> v;
        for (int i = 0; i < n; ++i) {
            long long tmp;
            cin >> tmp;
            v.push_back(tmp);
        }
        sort(v.begin(), v.end());

        dp[0] = 0;
        dp[4] = v[3] - v[0];
        dp[6] = v[5] - v[0];
        dp[8] = v[7] - v[4] + dp[4];
        for (int i = 10; i <= n; i += 2)
            dp[i] = min(dp[i - 4] + v[i - 1] - v[i - 4], dp[i - 6] + v[i - 1] - v[i - 6]);
        cout << dp[n] * 2 << endl;
    }
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int test_index_for_debug = 1;
    char acm_local_for_debug;
    while (cin >> acm_local_for_debug) {
        if (acm_local_for_debug == '$') exit(0);
        cin.putback(acm_local_for_debug);
        if (test_index_for_debug > 20) {
            throw runtime_error("Check the stdin!!!");
        }
        auto start_clock_for_debug = clock();
        solve();
        auto end_clock_for_debug = clock();
        cout << "Test " << test_index_for_debug << " successful" << endl;
        cerr << "Test " << test_index_for_debug++ << " Run Time: "
             << double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    }
#else
    solve();
#endif
    return 0;
}

事后发现其实代码有越界的问题……但是它AC了