洛谷 P4068 数字配对

本文介绍了一道洛谷温馨安利题目的解题思路,通过构建二分图模型并利用最大费用最大流算法求解。具体地,将数字按质因数分解后的指数之和的奇偶性分组,构建图模型并使用SPFA算法寻找增广路径,最终通过贪心策略找到最优解。

目录:

题目:

洛谷温馨安利

分析:

分析题目,发现重点在于条件「一个数字只能参与一次配对」。
考虑求出 cnti ,表示 ai 分解质因数之后,每个质因数的指数之和。

那么 aia aj 能配对的条件转化为:
ai aj 的倍数,且 cnti=cntj+1
考虑一个二分图的模型。先按照 cnt 的奇偶性,把数字分为两个集合。
1、源点向所有 cnt 为奇数的点连一条容量为 bi ,费用为0的边。
2、所有 cnt 为偶数的点向汇点连一条容量为 bi ,费用为0的边。
3、对于一对 i,j ,如果 ai aj 能配对并且 cnti 为奇数,则由 i j连一条边,容量为 ,费用为 ci×cj
然后跑最大费用最大流。但是写法有一些变化:
由于跑最大费用最大流的过程中,每一次增广求出的最长路一定不会大于上一次增广求出的最长路,所以考虑贪心
每一次跑最长路后,沿着最长路,在价值总和不小于0的前提下尽可能增加流量。如果找不到增广路或者继续增广一定会使价值总和小于0 ,则已经传输的总流量就是答案。

AC后感想:

是真的难,还是学习大佬的算法才AC的。。。

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef LL ll;
const int N = 210, M = 5e5 + 5; const ll INF = 1ll << 61;
int n, a[N], b[N], c[N], CNT[N], WWWYC = 1, nxt[M], WBD[N], st[M], go[M],
frm[M], S, T, len, que[M];
ll cap[M], DLJ[M], dis[N], sum, ans; bool vis[N];
void add_edge(int u, int v, ll w, ll x) {
    nxt[++WWWYC] = WBD[u]; WBD[u] = WWWYC; st[WWWYC] = u;
    go[WWWYC] = v; cap[WWWYC] = w; DLJ[WWWYC] = x;
    nxt[++WWWYC] = WBD[v]; WBD[v] = WWWYC; st[WWWYC] = v;
    go[WWWYC] = u; cap[WWWYC] = 0; DLJ[WWWYC] = -x;
}
int sigma(int n) {
    int i, S = sqrt(n), tot = 0; for (i = 2; i <= S; i++)
        while (n % i == 0) n /= i, tot++; if (n > 1) tot++; return tot;
}
bool spfa() {
    int i; for (i = S; i <= T; i++) vis[i] = 0, dis[i] = -INF;
    dis[que[len = 1] = S] = 0; for (i = 1; i <= len; i++) {
        int u = que[i]; vis[u] = 0;
        for (int e = WBD[u], v; e; e = nxt[e])
            if (cap[e] && dis[u] + DLJ[e] > dis[v = go[e]]) {
                dis[v] = dis[u] + DLJ[frm[v] = e];
                if (!vis[v]) vis[que[++len] = v] = 1;
            }
    }
    return dis[T] > -INF;
}
bool add() {
    ll fl = INF, delta; for (int e = frm[T]; e; e = frm[st[e]])
        fl = min(fl, cap[e]); delta = dis[T] * fl;
    if (sum + delta >= 0) {
        sum += delta; ans += fl;
        for (int e = frm[T]; e; e = frm[st[e]])
            cap[e] -= fl, cap[e ^ 1] += fl; return 1;
    }
    else return ans += sum / (-dis[T]), 0;
}
ll solve() {
    while (spfa() && add()); return ans;
}
int main() {
    int i, j; n = read(); for (i = 1; i <= n; i++) a[i] = read();
    for (i = 1; i <= n; i++) b[i] = read();
    for (i = 1; i <= n; i++) c[i] = read(); S = 1; T = n + 2;
    for (i = 1; i <= n; i++) CNT[i] = sigma(a[i]);
    for (i = 1; i <= n; i++) if (CNT[i] & 1) add_edge(S, i + 1, b[i], 0);
        else add_edge(i + 1, T, b[i], 0);
    for (i = 1; i <= n; i++) if (CNT[i] & 1) for (j = 1; j <= n; j++)
        if ((CNT[i] + 1 == CNT[j] && a[j] % a[i] == 0) ||
            (CNT[j] + 1 == CNT[i] && a[i] % a[j] == 0))
                add_edge(i + 1, j + 1, INF, 1ll * c[i] * c[j]);
    cout << solve() << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值