把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4514】[SDOI2016] 数字配对(二分图+非负费用最大流)

点此看题面

大致题意: 给定\(n\)种数字,每种数字有对应的数量以及相应的权值。规定两个数字若满足成倍数关系且相除得到的数是质数,则将他们匹配可以得到两数权值之积的价值。求在价值非负的前提下最多进行多少次匹配。

前言

作为重拾网络流的第一题,一开始题面看错\(WA\)了一发,然后\(INF\)设小又\(WA\)了一发。。。

二分图

首先,显然我们可以对能够匹配的数两两连边,然后发现这似乎是一张无向图,无法跑网络流,这就非常难受了。。。

但实际上,由于要求两数相除是质数,那我们求出所有数的质因子个数,就会发现能匹配的两数质因子个数必然差\(1\)

也就是说,我们可以根据质因子个数的奇偶性,把这张无向图变成一张二分图。

而既然是二分图,我们就可以人为定向,然后跑网络流了。

非负费用最大流

非负费用最大流实际上是我瞎取的一个名字。

实际上,我们只要\(SPFA\)跑最大费用最大流,并开两个变量\(res\)\(tot\)分别统计答案。

众所周知,每次跑完\(SPFA\),我们需要将\(res\)\(tot\)分别加上\(F\)\(F\times C\)\(F\)\(C\)分别表示流量和单位流量的价值)。

根据网络流的原理,我们知道,\(C\)一定是递减的。也就是说,当某一刻\(tot<0\)了,由于\(F\)必然大于\(0\),因此\(C\)只能小于\(0\),那么\(tot\)就只会越来越小,再也不可能满足非负的性质了。

所以,在给\(tot\)加上\(F\times C\)之前,我们就要判断\(tot+F\times C\)是否小于\(0\),如果小于\(0\),我们给\(res\)加上\(\lfloor\frac{tot}{-C}\rfloor\)(即尽可能再多流一下),然后结束最大流。

其他东西其实就和普通的费用流基本一致了。具体实现详见代码。

代码

#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;
}
posted @ 2020-05-21 08:07  TheLostWeak  阅读(138)  评论(0)    收藏  举报