【SDOI 2016】数字配对

戳一戳


Update \text{Update} Update

搞了个更好看更易懂的版本~

考虑网络流。思考如何判断数字配对的条件 —— 朴素显然是 O ( n 2 V ) \mathcal O(n^2\sqrt V) O(n2V ) 的,难以接受。

试图将质因数分解从二层循环抛到外面去:其实可以发现,配对条件满足当且仅当存在整除关系且质因数个数和相差为 1 1 1。这个问题就解决了。

如何连边?通过上文不难发现这是一张二分图,按质因数个数的奇偶性分类。于是要求最大化配对就变成了最大流。由于需要保证获得的价值总和不小于 0 0 0,我们优先选择费用更大的,也即 “最大费用最大流”。这个可以用 spfa \text{spfa} spfa 来解决。


感觉自己调了半天然后模板打错了。。。好难过。rsduheiutfhnesrfnsjkenfkj

不bibi了我们讲一下如何建图。我们可以发现这2个数字之间的关系是双向的。那我们怎么办呢~~(手动滑稽 )这里有一个很神奇的结论:如果a为b的因数且b除以a的值为质数,那么将a与b质因数分解后a与b的指数差为1。

其实证明很简单,若使除出来的值为质数,那首先a肯定将b的所有质因子个数-1全部包含,而最后的一个质因子也肯定只能差一个,差多个那么除出来的值会是最后一个质因子的几次方。

那我们就好建图了。求出所有数的质因数分解出的指数和,若指数和差1且整除就可连边。

最后,题目写的并不是求最大费用最大流,我们需要转化一下。详见代码。

#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<climits>
#define int long long
typedef long long ll;
using namespace std;

const int N = 5002, M = 900002, lim = LONG_LONG_MAX >> 1;
ll val[M];
bool vis[N];
int tot[N], a[N], b[N], c[N], prep[N], pree[N], dis[N], s, t, n, cnt, flow[M], to[M], head[N], nxt[M];

inline int read() {
    int x = 0, f = 1;
    char s = getchar();
    while(! isdigit(s)) {
        if(s == '-')
            f = -1;
        s = getchar();
    }
    while(isdigit(s)) {
        x = (x << 1) + (x << 3) + (s ^ 48);
        s = getchar();
    }
    return x * f;
}

inline void print(ll x) {
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}

inline int Min(const int x, const int y) {
    if(x < y)
        return x;
    return y;
}

inline void addEdge(const int u, const int v, const int w, const ll Val) {
    to[++ cnt] = v;
    nxt[cnt] = head[u];
    flow[cnt] = w;
    val[cnt] = Val;
    head[u] = cnt;
    to[++ cnt] = u;
    nxt[cnt] = head[v];
    flow[cnt] = 0;
    val[cnt] = -Val;
    head[v] = cnt;
}

inline bool SPFA() {
	queue <int> q;
	while(! q.empty())
		q.pop();
	for(int i = 1; i <= t; ++ i)
		dis[i] = -lim;
	memset(vis, 0, sizeof(int) * (t + 1));
	prep[t] = -1;
	dis[s] = 0;
	vis[s] = 1;
	q.push(s);
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for(int i = head[u]; ~i; i = nxt[i]) {
			int v = to[i];
			if(flow[i] > 0 && dis[v] < dis[u] + val[i]) {
				dis[v] = dis[u] + val[i];
				pree[v] = i;
				prep[v] = u;
				if(! vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	return ~prep[t];
}

inline int div(int x) {
	int res = 0;
	for(long long i = 2; i * i <= x; ++ i) {
		while(x % i == 0) {
			x /= i;
			++ res;
		}
	}
	if(x > 1)
		++ res;
	return res;
}

inline int jdz(const int x) {
	if(x < 0)
		return -x;
	return x;
}

ll EK() {
	ll ans = 0, sumflow = 0, maxflow;
	while(SPFA()) {
		maxflow = lim;
		for(int i = t; i != s; i = prep[i])
			maxflow = Min(maxflow, flow[pree[i]]);
		for(int i = t; i != s; i = prep[i]) {
			flow[pree[i]] -= maxflow;
			flow[pree[i] ^ 1] += maxflow;
		}
		if(ans + 1ll * maxflow * dis[t] < 0) {
			sumflow += (ans / -dis[t]);
			break;
		}//此时dis[t]为负,我们尽量让它多跑几个流,下面是复杂些的写法。至于为什么不用判断lim,是因为太大就除没了。
		ans += 1ll * maxflow * dis[t];
		sumflow += maxflow;
	}
	/*if(dis[t] != -lim)
		while(ans + dis[t] >= 0) {
			++ sumflow;
			ans += dis[t];
		}*/
	return sumflow;
}

signed main() {
	n = read();
	s = n + 1;
	t = s + 1;
	cnt = -1;
	memset(head, -1, sizeof(int) * (t + 1)); 
	for(int i = 1; i <= n; ++ i) {
		a[i] = read();
		tot[i] = div(a[i]);
	}
	for(int i = 1; i <= n; ++ i)
		b[i] = read();
	for(int i = 1; i <= n; ++ i)
		c[i] = read();
	for(int i = 1; i <= n; ++ i) {
		if(! (tot[i] & 1)) {
			addEdge(i, t, b[i], 0);
			continue;
		}
		addEdge(s, i, b[i], 0);
		for(int j = 1; j <= n; ++ j) {
			if(tot[j] & 1)
				continue;
			if(jdz(tot[i] - tot[j]) == 1 && (a[i] % a[j] == 0 || a[j] % a[i] == 0))
				addEdge(i, j, lim, 1ll * c[i] * c[j]);
		}
	}
	print(EK());
	putchar('\n');
    return 0;
}

bye~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值