P4068 [SDOI2016]数字配对

题意描述:

洛谷

\(n\) 个数字,每个数字的权值为 \(a_i\), 有 \(b_i\) 个,价值为 \(c_i\)

如果 \(a_i\)\(a_j\) 的倍数,且 \(a_i\ /a_j\) 为质数,则称 \(a_i\)\(a_j\) 可以配对成功,价值为 \(c_i \times c_j\)

每个数字只能匹配一次。 在获得的总价值不大于 \(0\) 的情况下,最多能匹配多少次。

数据范围: \(n\leq 200,a_i\leq 10^9,|c_i|\leq 10^5\)

solution:

最大费用最大流。

每个数字只能匹配一次,且个数有限制,每次匹配有价值,很容易想到费用流求解。

把每一次匹配当成一个流量,价值就是匹配所得到的价值。

然后 对于能成功匹配的 \(i\)\(j\) 两点之间连一条容量为 \(inf\), 费用为 \(c_i\times c_j\) 的边。

但我们不能确定每个数是位于左边还是右边。

比价笨的办法就是在跑 \(dinic\) 之前,跑一遍二分图染色。

对于左侧的点 \(i\),由源点向 \(i\) 连一条容量为 \(a_i\) 费用为 \(0\) 的边,反之位于右侧的点,则由点 \(i\) 向汇点连容量为 \(a_i\) 费用为 \(0\) 的边。

直接跑最大费用最大流肯定不对,因为求的是费用非负时的最大流。

考虑贪心一波,假设我们这次增广路的流量为 \(flow[t]\) ,单位费为 \(dis[t]\) ,当前收益为 \(maxcost\) .

如果 \(dis[t] >0\) ,很显然这样的匹配肯定是流量越多越好。

如果 \(dis[t] < 0\) 我们每次匹配是要损失一些代价的,当前收益最多能支持我们进行的匹配次数为 \(min(flow[t],{maxcost\over -dis[t]})\) , 答案直接加上这个次数即可。

如果 \(maxcost < 0\) 即费用为负,不能再继续匹配下去了,直接 \(break\) 掉。

code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
#define int long long
const int inf = 1e18;
const int N = 1e5+10;
int n,maxflow,maxcost,s,t,tot = 1;
int head[N],a[N],b[N],c[N],dis[N],flow[N],pre[N],last[N],col[N];
bool vis[N],check[510][510];
struct node
{
	int to,net,w,c;
}e[2000010];
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y,int w,int c)
{
	e[++tot].to = y;
	e[tot].w = w;
	e[tot].c = c;
	e[tot].net = head[x];
	head[x] = tot;
}
bool pd(int x)
{
	for(int i = 2; i <= sqrt(x); i++)
	{
		if(x % i == 0) return 0;	
	} 
	return 1;
} 
void dfs(int x,int w)
{
	col[x] = w;
	if(w == 1) add(s,x,b[x],0), add(x,s,0,0);
	else add(x,t,b[x],0), add(t,x,0,0);
	for(int i = 1; i <= n; i++)
	{
		if(check[x][i] && !col[i]) dfs(i,3-w); 
	}
}
bool spfa()
{
	queue<int> q;
	for(int i = 0; i <= t; i++) dis[i] = -inf, flow[i] = inf;
	for(int i = 0; i <= t; i++) vis[i] = 0, pre[i] = last[i] = -1;
	q.push(s); dis[s] = 0, vis[s] = 1;
	while(!q.empty())
	{
		int x = q.front(); q.pop(); vis[x] = 0;
		for(int i = head[x]; i; i = e[i].net)
		{
			int to = e[i].to;
			if(e[i].w && dis[to] < dis[x] + e[i].c)
			{
				dis[to] = dis[x] + e[i].c;
				flow[to] = min(flow[x],e[i].w);
				pre[to] = x, last[to] = i; 
				if(!vis[to]){q.push(to); vis[to] = 1;}
			} 
		}
	} 
	return pre[t] != -1;
}
void mcmf()
{
	while(spfa())
	{
		maxcost += flow[t] * dis[t];
		maxflow += flow[t];
		if(maxcost < 0)
		{
			maxcost -= flow[t] * dis[t];
			maxflow -= flow[t];
			maxflow += maxcost / (-dis[t]);
			break;
		}
		int x = t;
		while(x)
		{
			e[last[x]].w -= flow[t];
			e[last[x] ^ 1].w += flow[t];
			x = pre[x];
		}
	}
}
signed main()
{
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	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++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(i == j) continue;
			if((a[i] % a[j] == 0 && pd(a[i]/a[j]))) check[i][j] = check[j][i] = 1;
		}
	}
	s = 0, t = n+1;
	for(int i = 1; i <= n; i++) if(!col[i]) dfs(i,1); 
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(col[i] == 1 && check[i][j]) add(i,j,inf,c[i]*c[j]), add(j,i,0,-c[i]*c[j]);
		}
	}
	mcmf();
	printf("%lld\n",maxflow);
	return 0;
}
posted @ 2021-02-17 20:41  genshy  阅读(79)  评论(0编辑  收藏  举报