[SDOI2016]数字配对

一、题目

点此看题

二、解法

考虑这个条件ai是aj的倍数,且ai/aj是一个质数,满足这个条件就必须要满足下面两个条件:

  • a i a_i ai a j a_j aj的倍数,好像是废话
  • 设一个数质因数分解后的指数之和为 c n t cnt cnt,则 c n t i = c n t j + 1 cnt_i=cnt_j+1 cnti=cntj+1

第二个条件很重要,它告诉我们可以把数字的 c n t cnt cnt奇偶划分,就能得到一个二分图,我们就可以想网络流的方面想,图是这样建的:

  • 源点连奇数区,边权为 0 0 0,容量为数出现的数量。
  • 偶数区连汇点,边权为 0 0 0,容量为数出现的数量。
  • 奇数区和偶数区满足条件的数对连接,边权为 c i × c j c_i\times c_j ci×cj,容量为 i n f inf inf

然后我们在建好的图上跑费用流,由于图是二分图,最长路一定是单调递减的,于是跑费用流是判断费用用大于 0 0 0,这里的费用流写法不同,应写成最大费用最大流。

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <cmath>
#define int long long
#define inf (1ll<<60)
const int MAXN = 205;
using namespace std;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,tot,S,T,f[MAXN],a[MAXN],b[MAXN],c[MAXN];
int flow[MAXN],dis[MAXN],pre[MAXN],lst[MAXN]; 
struct edge
{
	int v,c,f,next;
}e[MAXN*MAXN];
struct node
{
	int u,c;
	bool operator < (const node &R) const
	{
		return c<R.c; 
	}
};
vector<node> A,B;
void add_edge(int u,int v,int c,int fl)
{
	e[++tot]=edge{v,c,fl,f[u]},f[u]=tot;
	e[++tot]=edge{u,-c,0,f[v]},f[v]=tot; 
}
int dec(int x)
{
	int cnt=0,t=sqrt(x);
	for(int i=2;i<=t;i++)
		while(x%i==0)
		{
			cnt++;
			x/=i;
		}
	if(x!=1) cnt++;
	return cnt;
}
bool pd(int x,int y)
{
	if(x%y==0 && dec(x/y)==1) return 1;
	if(y%x==0 && dec(y/x)==1) return 1;
	return 0;
}
bool bfs()
{
	for(int i=S;i<=T;i++)
		dis[i]=-inf;
	flow[S]=inf;dis[S]=0;pre[S]=-1;
	priority_queue<node> q;
	q.push(node{S,0});
	while(!q.empty())
	{
		int u=q.top().u,t=q.top().c;
		q.pop();
		if(dis[u]>t) continue;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v,c=e[i].c;
			if(dis[v]<dis[u]+c && e[i].f>0)
			{
				dis[v]=dis[u]+c;
				pre[v]=u;
				lst[v]=i;
				flow[v]=min(flow[u],e[i].f);
				q.push(node{v,dis[v]});
			}
		}
	}
	return dis[T]!=-inf;
}
int get()
{
	int sum=0,cost=0;
	while(bfs())
	{
		if(cost+flow[T]*dis[T]>=0)
		{
			cost+=flow[T]*dis[T];
			sum+=flow[T];
			int cur=T;
			while(cur^S)
			{
				e[lst[cur]].f-=flow[T];
				e[lst[cur]^1].f+=flow[T];
				cur=pre[cur];
			}
		}
		else return sum+cost/(-dis[T]);
	}
	return sum;
}
signed main()
{
	n=read();tot=1;
	S=0;T=n+1;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(dec(a[i])%2)
			A.push_back(node{i,a[i]});
		else
			B.push_back(node{i,a[i]});
	}
	for(int i=1;i<=n;i++)
		b[i]=read();
	for(int i=1;i<=n;i++)
		c[i]=read();
	for(int i=0;i<A.size();i++)
		add_edge(S,A[i].u,0,b[A[i].u]);
	for(int i=0;i<B.size();i++)
		add_edge(B[i].u,T,0,b[B[i].u]);
	for(int i=0;i<A.size();i++)
		for(int j=0;j<B.size();j++)
			if(pd(A[i].c,B[j].c))
				add_edge(A[i].u,B[j].u,c[A[i].u]*c[B[j].u],inf);
	printf("%lld\n",get());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值