CF1728F:Fishermen 题解(二分图最大权匹配)

1728F:Fishermen

题意:给你整数序列 a ,你可以让每个数乘上任意正数生成序列 b,使得 b 数值各不相同,求 b 序列最小和。


题解代码很短,但是复杂度比较匪夷所思。

Solution:

首先我们发现让 \(a_i\) 乘上比 n 还大的数这种情况是不存在的,因为如果 \(a_i\) 乘了 n+1,其他数字只有 n-1 个,不可能占完前面 \(a_i, 2a_i,3a_i...na_i\) 这 n 个位置。因此,每个数字最多乘上 n。

然后就是二分图匹配的模型,右边 n 个数字代表 a 序列,左边是 b 序列的所有可能值,每个 \(a_i\) 向左边的倍数连边,代表选择这个数,b 序列数值各不相同就是每个左边点只能匹配一条边。

如果我们让左边点连向的所有边权值都设为这个点的值,这道题的最终答案就是二分图的最大权匹配。

但是显然左边n^2个点,右边n个点,无论是费用流还是KM算法都很难跑的动。


然后是很关键的一步转化:最大权匹配转成普通的匹配。用到了贪心的思想。

我们把左边点从小到大排序,依次运行匈牙利算法进行匹配,当一个左边点匹配成功,它就一定不会被撤销,因为撤销它一个匹配关系最多可以让一个权值更大的点匹配,而不会让两个点找到增广路。因此,我们从小到大贪心地找增广路,答案即所有匹配的点权。


普通的二分图匹配算法是O(VE)的,即点权乘上边权,因为每个从左边点出发的DFS都会遍历整张图,左边点有n2个,图中边的数量也是n2,整体是n^4。

官方题解提供了一个适用的看似不起眼的小优化,但却使得复杂度变为了O(ME),其中M是匹配数,在这题中是 n,整体变为了n^3。

这个优化的内容就是,当一个点寻找增广路失败时,不去清空vis标记。因为寻找增广路失败时,图的结构没有改变,之后的点再访问到vis=1的点,就说明已经不能再找到增广路了。当找到一条增广路时,也就是一次匹配成功,再去清空vis标记。在这个优化下,整张图只会遍历M次,M为匹配数。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() int le=e[u].size();for(int i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#include <vector>
#include <queue>
#include <map>

using namespace std;
const int N=1010;
const int qwq=303030;
const int inf=0x3f3f3f3f;

inline int read() {
    int sum = 0, ff = 1; char c = getchar();
    while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
    while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
    return sum * ff;
}

int n;
int a[N];
int b[N*N],cnt;
vector <int> e[N*N];
bool vis[N];
int belong[N];

bool DFS(int u) {                // u -> left node   (1~n^2)
    for(int v : e[u]) {          // v -> right node  (1~n)
        if(vis[v]) continue;
        vis[v] = 1;
        if(!belong[v] || DFS(belong[v])) {
            belong[v] = u;
            return 1;
        }
    }
    return 0;
}

int main() {
    n = read();
    for(int i=1;i<=n;i++) a[i] = read();
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) b[++cnt] = a[i] * j;
    }
    sort(b+1,b+cnt+1);
    cnt = unique(b+1,b+cnt+1) - b - 1;
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            int id = lower_bound(b+1,b+cnt+1,a[i]*j) - b;
            // cout<<a[i]<<" * "<<j<<" id = "<<id<<endl;
            e[id].push_back(i);
        }
    }
    ll ans = 0, ci = 0;
    for(int i=1;i<=cnt;i++) {
        if(DFS(i)) {
            ans += b[i];
            ci++;
            if(ci==n) break;
            memset(vis,0,sizeof(vis));
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2024-07-18 01:51  maple276  阅读(16)  评论(0编辑  收藏  举报