BZOJ 1237 配对

Description

你有\(n\)个整数\(A_{i}\)\(n\)个整数\(B_{i}\)。你需要把它们配对,即每个\(A_{i}\)恰好对应一 个\(Bp_{i}\)。要求所有配对的整数差的绝对值之和尽量小,但不允许两个相同的数配对。例如\(A=\lbrace 5,6,8 \rbrace\)\(B=\lbrace 5,7,8 \rbrace\),则最优配对方案是\(5\)\(8\)\(6\)\(5\)\(8\)\(7\),配对整数的差的绝对值分别为\(2, 2, 1\),和为\(5\)。注意,\(5\)\(5\)\(6\)\(7\)\(8\)\(8\)是不允许的,因为相同的数不许配对。

Input

第一行为一个正整数\(n\),接下来是\(n\)行,每行两个整数\(A_{i}\)\(B_{i}\),保证所有\(A_{i}\)各不相同,\(B_{i}\)也各不相同。

Output

输出一个整数,即配对整数的差的绝对值之和的最小值。如果无法配对,输出\(-1\)

Sample Input

3
3 65
45 10
60 25

Sample Output

32

HINT

\(30\%\)的数据满足:\(n \le 10^{4}\)
\(100\%\)的数据满足:\(1 \le n \le 10^{5}\)\(A_{i}\)\(B_{i}\)均为\(1\)\(10^{6}\)之间的整数。

难得又一道自己想出的题目。
首先如果没有相同的数字不能同时配对,那么排序之后两两配对一定是最优的。那么我们在满足题目限制一定也要尽可能的满足题目的限制,相邻两个进行交换。
我们令\(pos_{i}\)表示前第\(i\)个排序后数字相同配对的位置,\(f_{i,0/1}\)表示前\(i\)个数字相同的配对,使其合法的最小增加代价(\(0\)表示与前面的交换,\(1\)表示与后面的交换)。转移有这样以下的几个:
\[f_{i,1} = min(f_{i-1,0},f_{i-1,1})+calc(pos_{i},pos_{i}+1)\]
\(pos_{i-1} \ge pos_{i}-1\)\[f_{i,0} = f_{i-1,0}+calc(pos_{i}-1,pos_{i})\]
否则\[f_{i,0} = min(f_{i-1,0},f_{i-1,1})+calc(pos_{i}-1,pos_{i})\]
产生分歧的原因是如果\(pos_{i-1} \ge pos_{i}-1\),那么如果\(i-1\)号非法配对与右边的交换,\(i\)号与左边的交换代价计算就会出错。
\(i \ge 2\)并且\(pos_{i}=pos_{i-1}+1\),我们可以两个非法的进行交换,得到以下的转移:
\(pos_{i-2} \ge pos_{i-1}-1\)时,\[f_{i,0} = min(f_{i,0},f_{i-2,0}+calc(pos_{i-1},pos_{i}))\]
否则\[f_{i,0} = min(f_{i,0},min(f_{i-2,0},f_{i-2,1})+calc(pos_{i-1},pos_{i}))\]
分歧的原因同上。

以上转移\(calc(a,b)\)为计算交换\(a,b\)位置增加代价的函数。

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

typedef long long ll;
#define inf (1LL<<60)
#define maxn 100010
int n,pos[maxn],tot; ll sum,A[maxn],B[maxn],f[maxn][2];

inline ll calc(int a,int b)
{
    if (!a||!b) return inf; if (a > n||b > n) return inf;
    return (abs(A[a]-B[b])+abs(A[b]-B[a]))-(abs(A[a]-B[a])+abs(A[b]-B[b]));
}
inline void dp()
{
    memset(f,0x7,sizeof(f));
    f[0][0] = f[0][1] = 0;
    for (int i = 1;i <= tot;++i)
    {
        f[i][1] = min(f[i-1][0],f[i-1][1])+calc(pos[i],pos[i]+1);
        if (pos[i-1]<pos[i]-1) f[i][0] = min(f[i-1][0],f[i-1][1])+calc(pos[i]-1,pos[i]);
        else f[i][0] = f[i-1][0]+calc(pos[i]-1,pos[i]);
        if (i >= 2&&pos[i] == pos[i-1]+1)
        {
            if (pos[i-2]<pos[i-1]-1) f[i][0] = min(f[i][0],min(f[i-2][0],f[i-2][1])+calc(pos[i-1],pos[i]));
            else f[i][0] = min(f[i][0],f[i-2][0]+calc(pos[i-1],pos[i]));
        }
    }
}

int main()
{
    freopen("1237.in","r",stdin);
    freopen("1237.out","w",stdout);
    scanf("%d",&n); for (int i = 1;i <= n;++i) scanf("%lld %lld",A+i,B+i);
    A[0] = A[n+1] = inf; pos[0] = -100;
    sort(A+1,A+n+1); sort(B+1,B+n+1);
    for (int i = 1;i <= n;++i)
    {
        sum += abs((A[i]-B[i]));
        if (A[i] == B[i]) pos[++tot] = i;
    }
    dp(); printf("%lld",sum+min(f[tot][0],f[tot][1]));
    fclose(stdin); fclose(stdout);
    return 0;
}

转载于:https://www.cnblogs.com/mmlz/p/4394922.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值