洛谷P3705 [SDOI2017]新生舞会 -二分答案+二分图带权最大匹配KM算法

【链接】
https://www.luogu.com.cn/problem/P3705
【题解】
要求 C = a 1 ′ + a 2 ′ + . . . + a n ′ b 1 ′ + b 2 ′ + . . . + b n ′ C = \frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n} C=b1+b2+...+bna1+a2+...+an的最大值,首先要想到二分,类似的还有最大(小)值最小/大、平均数最大(小)问题。
对原式进行移项操作 a 1 ′ + a 2 ′ + . . . + a n ′ b 1 ′ + b 2 ′ + . . . + b n ′ > = x \frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n} >= x b1+b2+...+bna1+a2+...+an>=x 转为 ∑ a i − x ∗ ∑ b i > = 0 \sum a_i - x * \sum b_i >= 0 aixbi>=0
二分 x x x 的值
重新建图,w[i][j] = A[i][j] - x*B[i][j]
使用KM算法检测当前最大匹配是否成功
可以用KM算法的原因,题目隐藏每个男女生都能找到舞伴的条件
【代码】

#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int INF = 1<<30;
const double eps = 1e-7;
int N,A[maxn][maxn],B[maxn][maxn];

double la[maxn],lb[maxn],W[maxn][maxn];
bool va[maxn],vb[maxn];
double delta = 0;
int match[maxn];
void read()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
            scanf("%d",&A[i][j]);
    for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
            scanf("%d",&B[i][j]);
}

bool dfs(int x)
{
    va[x] = 1;
    for(int y=1;y<=N;y++)
    {
        if(vb[y])   continue;
        if(la[x]+lb[y] - W[x][y] < eps)
        {
            vb[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
        else
        {
            delta = min(delta,la[x]+lb[y] - W[x][y]);//最小下降
        }
    }
    return false;
}
bool KM(double mid)
{   for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
            W[i][j] = 1.0*A[i][j] - mid*B[i][j];
    memset(match,0,sizeof(match));
    for(int i=1;i<=N;i++)
    {
        la[i] = -INF;
        lb[i] = 0;
        for(int j=1;j<=N;j++)
            la[i] = max(la[i],W[i][j]);
    }
    for(int i=1;i<=N;i++)
    {
       while(1)
       {
            memset(va,0,sizeof(va));
            memset(vb,0,sizeof(vb));
            delta = INF;
            if(dfs(i))  break;
            for(int j=1;j<=N;j++)
            {   if(va[j])   la[j]-=delta;
                if(vb[j])   lb[j]+=delta;
            }
       }
    }
    double ans = 0;
    for(int i=1;i<=N;i++)
        ans+=W[match[i]][i];
    return (ans>eps?1:0);
}
void work()
{
    double l = 0,r = 1e4,mid;
    while(r-l>eps)
    {
        mid = l+(r-l)/2;
        if(KM(mid))
            l = mid;
        else
            r = mid;
        
    }
    printf("%.6lf",l);
}
int main()
{
    read();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值