Fishermen CF1728F题解
首先,对于每个数 \(a\),都能与自己的倍数匹配,我们只需要令每个数所能匹配到的倍数的和最小,又因为每个数只能用一次,\(a\) 与 \(a\) 的倍数是一一对应的关系,所以我们想到二分图匹配。
考虑到最坏情况就是,这 \(n\) 个数都相同,那么最大匹配的就是 \(a\times n\),同时如果每个数的 \([a, a^2]\) 没有交集,则我们实际上会产生最多 \(n^2\) 个倍数。
再想贪心,因为要令和最小,则将所有倍数从小到大排序作为左部点,所有 \(a\) 作为右部点,从左到右扫一遍倍数集合,如果有匹配则令结果加上该倍数。注意当匹配数等于 \(n\) 时退出。
考虑为什么这样贪心是对的——
我们每次都选择当前最小的倍数,设该倍数为 \(u\),如果 \(u\) 匹配不上,说明右侧的数集中与之相连的数都已匹配,且右侧数集匹配的左部点无法增广,即右部点中未匹配的数没有左部点的因数。同时左部点中已匹配的点都是比 \(u\) 小的,这时候,如果在答案中加入 \(u\),一定会使结果变大。
如果该倍数匹配成功,设该倍数为 \(u\),那么说明右侧有仍未匹配的数,设该数为 \(k\), \(k\) 无法匹配上之前比 \(u\) 更小的数,且匹配比 \(u\) 大的数一定会令结果变大。
综上所述,该贪心正确。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1050, M = 1e6+100;
inline int read()
{
int x = 0; char ch = getchar();
while(ch<'0'||ch>'9'){ch = getchar();}
while(ch>='0'&&ch<='9'){x = x*10+ch-48; ch = getchar();}
return x;
}
int head[N*N], tot;
struct node
{
int nxt, to;
}edge[N*N*2];
void add(int u, int v)
{
edge[++tot].nxt = head[u];
edge[tot].to = v;
head[u] = tot;
}
int n;
int a[N];
int rit[N*N+N], num;
int mat[N*N+N];
bool vis[N*N+N];
bool dfs(int u)
{
for(int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!mat[v]||dfs(mat[v]))
{
mat[v] = u;
return 1;
}
}
return 0;
}
int xwx[N*N], qw;
int main()
{
n = read();
for(int i = 1; i<=n; i++)
a[i] = read();
sort(a+1, a+n+1);
num = n;
int last = 0;
for(int i = 1; i<=n; i++)
{
if(last == a[i]) continue;//去重
last = a[i];
for(int j = 1; j<=n; j++)
{
xwx[++qw] = a[i]*j;
}
}
sort(xwx+1,xwx+qw+1);
qw = unique(xwx+1, xwx+qw+1)-xwx-1;//还是去重,因为要离散化
for(int i = 1; i<=n; i++)
for(int j = n; j>=1; j--)
{
int pos = lower_bound(xwx+1, xwx+qw+1, a[i]*j)-xwx;
add(pos+n, i);
}
for(int i = 1; i<=qw; i++)
{
rit[++num] = xwx[i];
}
long long ans = 0;
for(int i = n, tt = 0; i <= num && tt <= n; i++)
{
if(dfs(i))
{
tt++;
memset(vis, 0, sizeof(vis));
ans+=rit[i];
}
}
printf("%lld\n", ans);
return 0;
}