P2507 [SCOI2008]配对

题目背景

四川NOI2008省选

题目描述

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

输入输出格式

输入格式:

第一行为一个正整数n,接下来是n 行,每行两个整数Ai和Bi,保证所有

Ai各不相同,Bi也各不相同。

输出格式:

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

出-1。

输入输出样例

输入样例#1: 
3
3 65
45 10
60 25
输出样例#1: 
32
输入样例#2: 
3
5 5
6 7
8 8
输出样例#2: 
5

说明

30%的数据满足:n <= 10^4

100%的数据满足:1 <= n <= 10^5,Ai和Bi均为1到10^6之间的整数。

 

Solution:

  这个贪心神了。。。`~`(前天写的题,本来是昨天应该写完的博客,结果因为这两天一直卡在几道题上,一直在刚题,忘记这回事了。五月断更了~)

  我们首先考虑没有限制条件,即不需要满足配对时$a_i\neq b_i$这个条件,那么很容易想到贪心的思路,直接对$a,b$从小到大排一遍序,然后累加$a,b$同项之差的绝对值即可。

  但是现在有了限制,各种方法骚,没有刚过,感觉完全不可做。

  后面仔细看题,发现一个超级重要的条件,保证$a$中数各不相同,$b$中数各不相同。

  那么就好搞了,我们先对$a,b$各自从小到大排一遍序,然后进行以下操作:

  首先考虑输出$-1$的情况:很显然只有当序列长度为$1$且$a_1=b_1$时无解,因为当$n\geq 2$时,由于数各不相同,那么同一个数$a,b$中顶多各自出现一次,我们就可以用后面的数和前面的数配对(显然一定不会相等)。

  其次,当有解时,我们贪心的想到,因为每个数顶多在$a,b$中各出现一次,所以当某次$a_i=b_i$时,则$a_i\neq b_{i-1}$且$a_i\neq b_{i+1}$($b_i$同理),于是我们可以让其和$a_{i-1},b_{i-1}$或者$a_{i+1},b_{i+1}$搭配,或者三个互相搭配,在中间取最小值就好了。(即使这三对数,每对相同,但由于$a,b$中数各自不同,那么这三对数一定可以搭配出两两不同的情况

  那么由于前两次需要判断边界,且$i+1$可能越界。于是将过程改为$a_i,a_{i-1},a_{i-2}$三个比较取最小值(都一样的)。

  排序后,求解的整个过程是线性的,于是用$DP$的思想来转移实现。

   定义状态$f[i]$表示前$i$个配对能得到的最小值,因为每次转移是三个比较,则初值$f[1]=|a_1-b_1|$,$f[2]=min(f[1]+|a2-b2|,|a1-b2|+|a2-b1|)$(注意当配对的两个数相等时,将其算出的值赋为$inf$)。

  那么不难得出状态转移方程:

  $f[i]=f[i-1]+|a_i-b_i|$(直接配对$a_i$和$b_i$)

  $f[i]=min(f[i],f[i-2]+|a_i-b_{i-1}|+|a_{i-1}-b_i|)$($i$和$i-1$配对)

  $f[i]=min(f[i],f[i-3]+|a_i-b_{i-2}|+|a_{i-1}-b_{i-1}|+|a_{i-2}-b_i|)$(三个配对时,让$i$和$i-2$配对)

  $f[i]=min(f[i],f[i-3]+|a_i-b_{i-2}|+|a_{i-1}-b_i|+|a_{i+1}-b_{i-1}|)$(三个配对时,$a,b$两两匹配的一种情况)

  $f[i]=min(f[i],f[i-3]+|a_i-b_{i-1}|+|a_{i-1}-b_{i-2}|+|a_{i+1}-b_i|)$(三个配对时,$a,b$两两匹配的另一种情况)

  最后目标状态$f[n]$就是答案。

代码:

 

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define Min(a,b) ((a)>(b)?(b):(a))
#define la(a,b) ((a!=b)?((a-b)>0?(a-b):(b-a)):233333333)
using namespace std;
const int N=100005;
ll n,f[N],a[N],b[N];

il int gi(){
    int a=0;char x=getchar();bool f=0;
    while((x<'0'||x>'9')&&x!='-')x=getchar();
    if(x=='-')x=getchar(),f=1;
    while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar();
    return f?-a:a;
}

int main(){
    n=gi();
    For(i,1,n) a[i]=gi(),b[i]=gi();
    sort(a+1,a+n+1);sort(b+1,b+n+1);
    if(a[1]==b[1]&&n==1){cout<<-1;return 0;}
    f[1]=la(a[1],b[1]);
    f[2]=Min(f[1]+la(a[2],b[2]),la(a[1],b[2])+la(a[2],b[1]));
    For(i,3,n){
        f[i]=f[i-1]+la(a[i],b[i]);
        f[i]=Min(f[i],f[i-2]+la(a[i],b[i-1])+la(a[i-1],b[i]));
        f[i]=Min(f[i],f[i-3]+la(a[i],b[i-2])+la(a[i-2],b[i])+la(a[i-1],b[i-1]));
        f[i]=Min(f[i],f[i-3]+la(a[i],b[i-1])+la(a[i-1],b[i-2])+la(a[i-2],b[i]));
        f[i]=Min(f[i],f[i-3]+la(a[i],b[i-2])+la(a[i-1],b[i])+la(a[i-2],b[i-1]));
    }
    cout<<f[n];
    return 0;
}

 

 

 

 

 

转载于:https://www.cnblogs.com/five20/p/9116359.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值