zoukankan      html  css  js  c++  java
  • 【BZOJ4514】[SDOI2016] 数字配对(二分图+非负费用最大流)

    点此看题面

    大致题意: 给定(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;
    }
    
  • 相关阅读:
    撕裂寂寞
    创业中的“孙子兵法”
    生命的颜色占卜
    常常激励我们的36句话
    创建自己的3D虚拟身体!
    富人和穷人的八大差异
    有时,孤单是一种享受
    JavaScript类
    上网的十条基本礼节
    程序设计中的感悟
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4514.html
Copyright © 2011-2022 走看看