[SDOI2016]数字配对
[SDOI2016]数字配对
[题目链接]
[思路要点]
神仙构图(一点也不神仙)
毫无疑问是一道网络流题
很明显的费用流结构,每种数字单独建一个点,两个点代表的数字能够配对那么连一条边,边权就是 \(c_ic_j\)
但是问题在于从源点和汇点出发的边不知道怎么连
但是如果将目前连的图画出来,你会惊讶地发现这是一个二分图
仔细思考你会发现,可以根据质因数的个数的奇偶性分类,由于两个数能配对当且仅当其质因数集合正好相差一个质数,那么质因数个数一定是一个奇数一个偶数
于是我们得到一个图,现在要求的是这个图的总费用非负情况下的最大流
考虑经典的最大费用流的算法,每次找到当前费用最大的一条路径并增广直到发现不了从 \(s\) 到 \(t\) 的流量大于 \(1\) 的路径
那么我们一个显然的想法是,如果当前寻到的路径的费用大于等于零,显然流满。然后记录下当前的费用,一直流到当前寻到的路径的费用与流量乘积使总费用小于零,此时就尽量流,流当前费用除以这条路径费用下取整这么多流量,然后你会发现这显然是对的
[代码]
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#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<int> 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));
}