zoukankan      html  css  js  c++  java
  • 【洛谷P4068】数字配对

    题目

    题目链接:https://www.luogu.com.cn/problem/P4068
    \(n\) 种数字,第 \(i\) 种数字是 \(a_i\)、有 \(b_i\) 个,权值是 \(c_i\)
    若两个数字 \(a_i\)\(a_j\) 满足,\(a_i\)\(a_j\) 的倍数,且 \(\frac{a_i}{a_j}\) 是一个质数,
    那么这两个数字可以配对,并获得 \(c_i \times c_j\) 的价值。
    一个数字只能参与一次配对,可以不参与配对。
    在获得的价值总和不小于 0 的前提下,求最多进行多少次配对。

    思路

    我们把每一个数字按照分解质因数后质因子(不去重)的数量的奇偶性分为两类,显然同一类中的任意两个数字不能配对,因为他们的商一定有偶数个质因子,所以商一定不是质数。
    那么这两个集合就是一个二分图。我们考虑如下构建:

    • 设一个 \(a[i]\) 的质因子有奇数个,从源点向 \(i\) 连一条流量为 \(c[i]\),费用为 0 的边。
    • 设一个 \(a[i]\) 的质因子有偶数个,从 \(i\) 向汇点连一条流量为 \(c[i]\),费用为 0 的边。
    • 如果不同集合的两个点 \(i,j\)可以配对(设 \(i\) 的质因子有奇数个),那么从 \(i\)\(j\) 连一条流量为 \(+\infty\),费用为 \(c[i]\times c[j]\) 的边。

    接下来跑最大费用最大流即可。最大流即为答案。
    但是这个费用流与一般的费用流有所不同,因为一般的费用流要求是费用最大/小的时候的最大流,而此题要求“价值总和不小于 0”,也就是费用不小于 0 时的最大流。
    显然每一次增广时的最长路不超过上一次增广的最长路,如果这条最长路长度非负,那么显然越多越好;否则肯定要在费用不小于 0 的前提下越大越好,因为后面的增广最长路会更短,所以收益更小。

    代码

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    
    const int N=210,M=6e5,Inf=1e9;
    int a[N],b[N],c[N],head[N],p[N],pre[N];
    int n,S,T,tot=1;
    ll maxflow,cost,dis[N];
    bool vis[N];
    
    struct edge
    {
    	int next,to,from,flow;
    	ll cost;
    }e[M];
    
    void add(int from,int to,int flow,ll cost)
    {
    	e[++tot].to=to;
    	e[tot].from=from;
    	e[tot].flow=flow;
    	e[tot].cost=cost;
    	e[tot].next=head[from];
    	head[from]=tot;
    }
    
    bool spfa()
    {
    	memset(dis,-0x3f3f3f3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	queue<int> q;
    	q.push(S); dis[S]=0;
    	while (q.size())
    	{
    		int u=q.front();
    		q.pop();
    		vis[u]=0;
    		for (int i=head[u];~i;i=e[i].next)
    		{
    			int v=e[i].to;
    			if (e[i].flow && dis[v]<dis[u]+e[i].cost)
    			{
    				dis[v]=dis[u]+e[i].cost;
    				pre[v]=i;
    				if (!vis[v])
    				{
    					q.push(v);
    					vis[v]=1;
    				}
    			}
    		}
    	}
    	return dis[T]>-1e18;
    }
    
    bool addflow()
    {
    	ll minflow=1e18;
    	for (int x=T;x!=S;x=e[pre[x]].from)
    		minflow=min(minflow,1LL*e[pre[x]].flow);
    	if (dis[T]<0) minflow=min(minflow,cost/(-dis[T]));
    	for (int x=T;x!=S;x=e[pre[x]].from)
    	{
    		e[pre[x]].flow-=minflow;
    		e[pre[x]^1].flow+=minflow;
    	}
    	maxflow+=minflow;
    	cost+=minflow*dis[T];
    }
    
    void mcmf()
    {
    	while (spfa())
    	{
    		if (cost+dis[T]<0) return;
    		addflow();
    	}
    }
    
    int main()
    {
    	S=N-1; T=N-2;
    	memset(head,-1,sizeof(head));
    	scanf("%d",&n);
    	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for (int i=1;i<=n;i++) scanf("%d",&b[i]);
    	for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    	for (int i=1;i<=n;i++)
    	{
    		int x=a[i];
    		for (int j=2;j*j<=x;j++)
    			for (;x%j==0;x/=j) p[i]++;
    		if (x>1) p[i]++;
    		if (p[i]&1) add(S,i,b[i],0),add(i,S,0,0);
    			else add(i,T,b[i],0),add(T,i,0,0);
    	}
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=n;j++)
    			if ((p[i]&1) && abs(p[i]-p[j])==1 && (!(a[i]%a[j]) || !(a[j]%a[i])))
    				add(i,j,Inf,1LL*c[i]*c[j]),add(j,i,0,-1LL*c[i]*c[j]);
    	mcmf();
    	printf("%lld\n",maxflow);
    	return 0;
    }
    
  • 相关阅读:
    PAT 甲级 1132 Cut Integer (20 分)
    AcWing 7.混合背包问题
    AcWing 9. 分组背包问题
    AcWing 5. 多重背包问题 II
    AcWing 3. 完全背包问题
    AcWing 4. 多重背包问题
    AcWing 2. 01背包问题
    AcWing 875. 快速幂
    AcWing 874. 筛法求欧拉函数
    AcWing 873. 欧拉函数
  • 原文地址:https://www.cnblogs.com/stoorz/p/12527030.html
Copyright © 2011-2022 走看看