SDOI2016 数字配对

传送门

裸费用流。
建边:对于 a [ i ] a [ j ] = p r i m e & a [ j ] ∣ a [ i ] \frac{a[i]}{a[j]}=prime \& a[j]|a[i] a[j]a[i]=prime&a[j]a[i],需要 i i i j + n j+n j+n连,并且 j j j i + n i+n i+n连。费用即为 c [ i ] ∗ c [ j ] c[i]*c[j] c[i]c[j],流量无穷大。而源点向左侧每个点连边,费用为0,流量为 b [ i ] b[i] b[i],右侧向汇点也如此。可以发现,这样相当于是每一种数的个数翻了一倍,所以最后答案除以2即可。
费用流就是每次按照费用找到一条最短路然后增广。
那么如果当前增广后的费用小于了0,那么就不流满,增加流量至费用刚好大等于0。否则可以全部流满。由于总费用是和0作比较,那么就不存在除以2的问题。

#include<bits/stdc++.h>
#define cs const
#define re register
#define ll long long
cs int N=205,M=162000,oo=1e9;
cs ll OO=1e18;
int n,key[N],num[N],S,T;ll val[N];
namespace IO{
	cs int Rlen=1<<22|1;
	char buf[Rlen],*p1,*p2;
	inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
	template<typename T>
	inline T get(){
		char ch=gc();T x=0;int f=1;
		while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
		while(isdigit(ch)) x=((x+(x<<2))<<1)+(ch^48),ch=gc();
		return x*f;
	}
	inline int gi(){return get<int>();}
	inline ll gl(){return get<ll>();}
}	
using IO::gi;
using IO::gl;
namespace math{
	cs int N=32000;
	int mark[N],P[N],cnt=0;
	inline void linear_sieves(){
		mark[0]=mark[1]=1;
		for(int re i=2;i<N;++i){
			if(!mark[i]) P[++cnt]=i;
			for(int re j=1;j<=cnt&&i*P[j]<N;++j){
				mark[i*P[j]]=1;if(i%P[j]==0) break;
			}
		}
	}
	inline bool isprime(int x){
		if(x<N) return !mark[x];
		for(int re j=1,up=sqrt(x);j<=cnt&&P[j]<=up;++j)
			if(x%P[j]==0) return false;
		return true;
	}
}
using math::isprime;

inline void Min(int &x,int y){if(x>y)x=y;}
namespace FLOW{
	cs int gN=::N<<1|1;
	int Head[gN],Next[M],V[M],W[M],cnt=1;ll C[M];
	int edge[gN],pre[gN],flow[gN],vis[gN];ll dis[gN];
	//w流量,c费用 
	inline void add(int u,int v,int w,ll c){Next[++cnt]=Head[u],V[cnt]=v,W[cnt]=w,C[cnt]=c,Head[u]=cnt;}
	inline bool SPFA(){
		std::queue<int> Q;
		for(int re i=1;i<=T;++i) flow[i]=oo,dis[i]=-OO;
		dis[S]=0,vis[S]=1,Q.push(S);
		while(!Q.empty()){
			int u=Q.front();Q.pop(),vis[u]=0;
			for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
				if(W[i]&&dis[v]<dis[u]+C[i]){
					dis[v]=dis[u]+C[i],edge[v]=i,pre[v]=u;
					Min(flow[v],flow[u]),Min(flow[v],W[i]);
					if(!vis[v]) Q.push(v),vis[v]=1;
				}
			}
		}return dis[T]!=-OO;
	}
	inline int mcf(int ans_flow=0,ll ans_cost=0){
		while(SPFA()){
			if(ans_cost+dis[T]*flow[T]<0){
				ans_flow+=ans_cost/(-dis[T]);break;
			}ans_flow+=flow[T],ans_cost+=dis[T]*flow[T];
			for(int re u=T;u!=S;u=pre[u])
				W[edge[u]]-=flow[T],W[edge[u]^1]+=flow[T];
		}return ans_flow/2;
	}
}
using FLOW::add;
int main(){
//	freopen("2560.in","r",stdin);
	n=gi(),S=n<<1|1,T=S+1,math::linear_sieves();
	for(int re i=1;i<=n;++i) key[i]=gi();
	for(int re i=1;i<=n;++i) num[i]=gi();
	for(int re i=1;i<=n;++i) val[i]=gl();
	for(int re i=1;i<=n;++i)
		for(int re j=1;j<=n;++j)
			if(key[i]%key[j]==0&&isprime(key[i]/key[j]))
				add(i,j+n,oo,val[i]*val[j]),
				add(j+n,i,0,-val[i]*val[j]),
				add(j,i+n,oo,val[i]*val[j]),
				add(i+n,j,0,-val[i]*val[j]);
	for(int re i=1;i<=n;++i) add(S,i,num[i],0),add(i,S,0,0);
	for(int re i=1;i<=n;++i) add(i+n,T,num[i],0),add(T,i+n,0,0);
	printf("%d \n",FLOW::mcf());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值