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

1728F:Fishermen

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


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

Solution:

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

然后就是二分图匹配的模型,右边 n 个数字代表 a 序列,左边是 b 序列的所有可能值,每个 ai 向左边的倍数连边,代表选择这个数,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; }

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/18308598.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示