BZOJ1237: [SCOI2008]配对

感觉此题还是挺可做的...

首先考虑最无脑的做法:

  我需要知道接下来要给谁配对,在另一个数组中还没有被选的有哪些,
  并记录已选的造成的贡献

要想知道没选的有哪些的话,这直接记录问题就很大了,考虑能不能省去这一步

那么无非是两个数组被选的集合的右端点同时往右扩张,
或是每个数可与其配对的数的范围大概为一个常数

直觉是这样的,考虑证明一下(其实我也不会证...)

先把两个数组排序,
这样对于一个 ai 来说他可选的一定是在 b 数组中的一段连续区间

在注意到每个数组中元素不重复之后,可以发现

显然特别极端的情况是不能可能的,比如

这是显然可以随意调整一下的,最后最左边的点一定是选择一个离他较近的点的

想证明其他的基本同理,试图枚举下面的点与上面的值是否相同即可

然后大概感觉上就没什么问题了,四个的话也是可以枚举值是否相同做的

好像就到 3 了,可以考虑 n = 3 且 a_i b_i 都相同的时候,
显然是三个交叉选要优的,要么就无解了。。

然后就粗糙的证完了,估计考场上也就这样了

关于保证两两配对,状态定义为 f[i] 表示给前 i 个配好对之后的最小代价
只要每次转移选完当前的从前边的状态转移过来就行


 代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cstdio>
#include<cmath>
using namespace std;

typedef long long ll;
const int MAXN = 100005;

int n;
ll a[MAXN], b[MAXN], f[MAXN];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld%lld", &a[i], &b[i]);
        f[i] = 200000000000000ll;
    }
    if(n == 1 && a[1] == b[1]) {
        puts("-1");
        return 0;
    }
    sort(a + 1, a + n + 1);
    sort(b + 1, b + n + 1);
    f[0] = 0ll;
    for(int i = 1; i <= n; ++i) {
        if(a[i] != b[i]) f[i] = f[i - 1] + abs(a[i] - b[i]);
        if(i >= 2 && a[i] != b[i - 1] && a[i - 1] != b[i]) f[i] = min(f[i], f[i - 2] + abs(a[i] - b[i - 1]) + abs(a[i - 1] - b[i]));
        if(i >= 3) {
            if((a[i] != b[i - 1]) && (a[i - 1] != b[i - 2]) && (a[i - 2] != b[i])) f[i] = min(f[i], f[i - 3] + abs(a[i] - b[i - 1]) + abs(a[i - 1] - b[i - 2]) + abs(a[i - 2] - b[i]));
            if((a[i] != b[i - 2]) && (a[i - 1] != b[i]) && (a[i - 2] != b[i - 1])) f[i] = min(f[i], f[i - 3] + abs(a[i] - b[i - 2]) + abs(a[i - 1] - b[i]) + abs(a[i - 2] - b[i - 1]));
        }
    }
    printf("%lld\n", (f[n] == 200000000000000ll ? -1 : f[n]));
    return 0;
}

  

posted @ 2018-09-29 22:00  EvalonXing  阅读(170)  评论(0编辑  收藏  举报