[SDOI2016]数字配对

[SDOI2016]数字配对

[题目链接]

链接

[思路要点]

神仙构图(一点也不神仙)

毫无疑问是一道网络流题

很明显的费用流结构,每种数字单独建一个点,两个点代表的数字能够配对那么连一条边,边权就是 \(c_ic_j\)

但是问题在于从源点和汇点出发的边不知道怎么连

但是如果将目前连的图画出来,你会惊讶地发现这是一个二分图

仔细思考你会发现,可以根据质因数的个数的奇偶性分类,由于两个数能配对当且仅当其质因数集合正好相差一个质数,那么质因数个数一定是一个奇数一个偶数

于是我们得到一个图,现在要求的是这个图的总费用非负情况下的最大流

考虑经典的最大费用流的算法,每次找到当前费用最大的一条路径并增广直到发现不了从 \(s\)\(t\) 的流量大于 \(1\) 的路径

那么我们一个显然的想法是,如果当前寻到的路径的费用大于等于零,显然流满。然后记录下当前的费用,一直流到当前寻到的路径的费用与流量乘积使总费用小于零,此时就尽量流,流当前费用除以这条路径费用下取整这么多流量,然后你会发现这显然是对的

[代码]

#include 
#include 
#include 
#include 
#include 
#define int long long

const int N = 1e3 + 31, M = 2e6 + 62, INF = 0x3f3f3f3f;
struct edge {
    int to, next, w, c;
} e[M << 1];
int head[N], cnt = 1;
void addedge(int x, int y, int z, int w) {
    e[++cnt] = (edge){y, head[x], z, w}, head[x] = cnt;
    e[++cnt] = (edge){x, head[y], 0, -w}, head[y] = cnt;
}
int ek(int s, int t) {
    static int dis[N], vis[N], pre[N], flow[N];
    std::queue q;
    int ret = 0, m = 0;
    while ("每日一膜:G♂LX") {
        memset(dis, 0xc0, sizeof dis);
        flow[s] = INF, dis[s] = 0;
        for (q.push(s); !q.empty();) {
            int x = q.front();
            vis[x] = 0;
            q.pop();
            for (int i = head[x]; i; i = e[i].next) {
                int nx = e[i].to;
                if (e[i].w && dis[nx] < dis[x] + e[i].c) {
                    dis[nx] = dis[x] + e[i].c;
                    flow[nx] = std::min(flow[x], e[i].w);
                    pre[nx] = i;
                    if (!vis[nx]) vis[nx] = 1, q.push(nx);
                }
            }
        }
        if (dis[t] == 0xc0c0c0c0) return ret;
        if (m + flow[t] * dis[t] < 0) return ret - m / dis[t];
        for (int i = pre[t]; i; i = pre[e[i ^ 1].to]) {
            e[i].w -= flow[t];
            e[i ^ 1].w += flow[t];
        }
        ret += flow[t];
        m += flow[t] * dis[t];
    }
}

int $(int x) {
    int ret = 0;
    for (int i = 2; i <= sqrt(x); i++)
        for (; x % i == 0; ret++) x /= i;
    return ret + (x > 1);
}
int n, a[N], b[N], c[N], f[N];
signed main() {
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
        f[i] = $(a[i]);
    }
    for (int i = 1; i <= n; i++) {
        scanf("%lld", b + i);
        if (f[i] & 1)
            addedge(1, i + 2, b[i], 0);
        else
            addedge(i + 2, 2, b[i], 0);
    }
    for (int i = 1; i <= n; i++) scanf("%lld", c + i);
    for (int i = 1; i <= n; i++) {
        if (f[i] & 1)
            for (int j = 1; j <= n; j++) {
                if ((f[i] == f[j] + 1 && a[i] % a[j] == 0) || (f[j] == f[i] + 1 && a[j] % a[i] == 0))
                    addedge(i + 2, j + 2, INF, c[i] * c[j]);
            }
    }
    printf("%lld", ek(1, 2));
}

转载于:https://www.cnblogs.com/wawawa8/p/11113563.html

你可能感兴趣的:([SDOI2016]数字配对)