大致题意: 给定(n)种数字,每种数字有对应的数量以及相应的权值。规定两个数字若满足成倍数关系且相除得到的数是质数,则将他们匹配可以得到两数权值之积的价值。求在价值非负的前提下最多进行多少次匹配。
前言
作为重拾网络流的第一题,一开始题面看错(WA)了一发,然后(INF)设小又(WA)了一发。。。
二分图
首先,显然我们可以对能够匹配的数两两连边,然后发现这似乎是一张无向图,无法跑网络流,这就非常难受了。。。
但实际上,由于要求两数相除是质数,那我们求出所有数的质因子个数,就会发现能匹配的两数质因子个数必然差(1)。
也就是说,我们可以根据质因子个数的奇偶性,把这张无向图变成一张二分图。
而既然是二分图,我们就可以人为定向,然后跑网络流了。
非负费用最大流
非负费用最大流实际上是我瞎取的一个名字。
实际上,我们只要(SPFA)跑最大费用最大流,并开两个变量(res)和(tot)分别统计答案。
众所周知,每次跑完(SPFA),我们需要将(res)和(tot)分别加上(F)和(F imes C)((F)和(C)分别表示流量和单位流量的价值)。
根据网络流的原理,我们知道,(C)一定是递减的。也就是说,当某一刻(tot<0)了,由于(F)必然大于(0),因此(C)只能小于(0),那么(tot)就只会越来越小,再也不可能满足非负的性质了。
所以,在给(tot)加上(F imes C)之前,我们就要判断(tot+F imes C)是否小于(0),如果小于(0),我们给(res)加上(lfloorfrac{tot}{-C} floor)(即尽可能再多流一下),然后结束最大流。
其他东西其实就和普通的费用流基本一致了。具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200
#define M 40000
#define LL long long
#define INF (int)1e9
#define add(x,y,f,c)
(
e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c
)
using namespace std;
int n,a[N+5],b[N+5],c[N+5],p[N+5],ee,lnk[N+5];struct edge {int to,nxt,F;LL C;}e[2*M+5];
class NonnegativeCostMaxFlow//非负费用最大流
{
private:
#define E(x) ((((x)-1)^1)+1)
int lst[N+5],IQ[N+5],F[N+5];LL C[N+5];queue<int> q;
I bool SPFA()//SPFA求增广路
{
RI i,k;for(i=1;i<=n+2;++i) F[i]=INF,C[i]=-1e18;q.push(S),C[S]=0;
W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
{
if(!e[i].F||C[k]+e[i].C<=C[e[i].to]) continue;
C[e[i].to]=C[k]+e[lst[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
}return F[T]^INF;
}
public:
int S,T;I void NCMF()
{
RI x,res=0;LL tot=0;W(SPFA())
{
if(tot+C[T]*F[T]<0) {res+=tot/(-C[T]);break;}res+=F[x=T],tot+=C[T]*F[T];//若不满足非负限制,结束最大流
W(x^S) e[lst[x]].F-=F[T],e[E(lst[x])].F+=F[T],x=e[E(lst[x])].to;
}printf("%d",res);
}
}D;
I int PCount(RI x) {RI i,t=0;for(i=2;i*i<=x;++i) W(!(x%i)) x/=i,++t;return t+(x!=1);}//统计质因子个数
int main()
{
RI i,j;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),p[i]=PCount(a[i]);
for(i=1;i<=n;++i) scanf("%d",b+i);for(i=1;i<=n;++i) scanf("%d",c+i);
for(D.S=n+1,D.T=n+2,i=1;i<=n;++i)
{
if(p[i]&1) add(D.S,i,b[i],0);else add(i,D.T,b[i],0);//根据在二分图的哪一边,和源/汇连边
for(j=1;j<=n;++j) !(a[i]%a[j])&&p[i]==p[j]+1&&//枚举点判断能否连边
(p[i]&1?add(i,j,INF,1LL*c[i]*c[j]):add(j,i,INF,1LL*c[i]*c[j]));//根据在二分图哪一边给边定向
}return D.NCMF(),0;
}