匹配问题 dp预处理

关键词:dp预处理:排序
题意:x轴上n个点与m个点之间进行匹配,保证1)n每个点都只能匹配一次2)m每个点都得有匹配对象(m<=n),已知两两点之间的匹配代价=坐标差得绝对值,求满足条件的最小匹配代价
解法:
失误:没有充分利用条件的特殊性:在x轴上!因此可以先把n个点和m个点分别在坐标轴上进行排序。
可以观察到连个端点之间一定相互匹配!
结论:x1<=x2,y1<=y2,则|x1-y1|+|x2-y2|<|x1-y2|+|x2-y1|
证明:简单处理一下就可以证出,略去
所以可以得到dp解法如下:
dp[i][j]:n个点中的前i个匹配m个点中的前j个的最小代价
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+abs(pos1[i]-pos2[j])

注意:
1.long long范围内的INF1使用 0x0fffffffffffffff (15个f,9*INF超越LL最大值)
2.int范围内的INF2使用0x3f3f3f3f(INF2*3超越INT最大值)

拓展1:改成求匹配代价最大值,如何求解?
解法:由上述绝对值不等式可知,排好序后m个点中的最大值一定与n个点中的最小值进行匹配,否则匹配代价一定还可以增大
因此只需要把m个点从小到大排列,n个点从大到小排列,dp状态转移方程中的min改为max即可!

拓展2:改成二维平面内的坐标点,匹配代价是曼哈顿距离,如何求解最小匹配值?

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#define mem(a,b) memset(a,sizeof(a),b)
#define ll long long
#define MP make_pair
using namespace std;
typedef long long lld;
const lld INF = 0x0fffffffffffffff ;
const int maxn = 4000+10;

int n,m;
struct Node{
    int id;
    long long pos;
};
bool cmp(Node a,Node b){
    return a.pos<b.pos;
}

Node que[maxn],pos[maxn];
long long  dp[maxn];
bool path[maxn][maxn];
int match[maxn];

void print(int n,int m){
    if(n==1) { match[que[1].id]=pos[1].id; return; }
    if(path[n][m]==0){
        print(n-1,m);
    }
    else if(path[n][m]==1){
        print(n-1,m-1);
    }
    match[que[n].id]=pos[m].id;
}

int abs(int a){
    if(a<0) return -a;
    return a;
}

int main(){
    //freopen("a.txt","r",stdin);
    while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=n;i++) { scanf("%lld",&que[i].pos); que[i].id=i; }
        scanf("%d",&m);
        for(int i=1;i<=m;i++) { scanf("%lld",&pos[i].pos); pos[i].id=i; }
        sort(que+1,que+n+1,cmp);
        sort(pos+1,pos+m+1,cmp);
        for(int j=0;j<=m;j++) dp[j]=INF;
        dp[1]=abs(que[1].pos-pos[1].pos);
        for(int i=2;i<=n;i++){
            for(int j=min(m,i);j>=1;j--){
                if(dp[j]<=dp[j-1]){ dp[j]=dp[j]+abs(que[i].pos-pos[j].pos); path[i][j]=0;}
                else { dp[j]=dp[j-1]+abs(que[i].pos-pos[j].pos); path[i][j]=1; }
            }
        }
        printf("%lld\n",dp[m]);
        print(n,m);
        for(int i=1;i<n;i++) printf("%d ",match[i]);
        printf("%d\n",match[n]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值