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;
}