【BZOJ4514】数字配对,费用流

传送门
题面:
这里写图片描述


写在前面:网络流练习太少……
思路:费用流,最大或最小随意,看你给费用的符号,建图的话是把数分成两部分,分别是奇数个质因子和偶数个质因子,然后通过题目给出的关系连边(分部分的原因是形成二分图,从而分别向源点和汇点连边),费用为(+-)c[i]*c[j](正负号根据跑的是最大费用还是最小费用来决定),流量正无穷,然后两部分分别向源点汇点连边,费用0,流量为b[i]。
注意:
1.两部分连边时需要判断a[i]/a[j]是不是质数,Shallwe的live版程序每次 O(a[i]/a[j]) 暴力求解,但当其取极限数据时可达到每次判断O(10^4.5), n2 的循环次数不能承受,所以我们可以筛出1-10^4.5的质数,然后每次求a[i]/a[j]是否在这个范围内有质因子就可以判断其是否为质数了
2.费用流终止条件为没有可流的边或费用总和不满足要求,所以我们要记录每次跑完最小(大)费用后的费用总和,且当它要超出规定范围时,我们要把答案加上临界值再退出(比如跑完一次最小费用后,之前答案的费用总和为-4,而这次要增加的为2(流量)*4(本次的费用),那么我们不能全部跑完2个流量,但可以只跑1个流量,所以要ans+=之前的费用总和/本次费用)
3.注意long long,不然会被卡成狗
代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define LL long long
#define Inf 10000000000000LL
using namespace std;
int n,tot=1,s,t;
int a[204],b[204],first[204],One[204],Two[204],up[204];
int prime[10010],ans;
LL dis[204],maxn,c[204];
bool vis[32004];
struct edge
{
    int u,v,w,next;
    LL cost;
}e[50000];
queue<int>q;
void add(int x,int y,int z,LL f)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].cost=f;
    e[tot].next=first[x];
    first[x]=tot;
}
void init()//筛出1-32000的质数
{
    for (int i=2;i<=32000;i++)
    {
        if (!vis[i]) prime[++prime[0]]=i;
        for (int j=1;j<=prime[0];j++)
        {
            if (i*prime[j]>32000) break;
            vis[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
}
bool pd(int i,int j)
{
    if (!a[i]||!a[j]||a[i]%a[j]&&a[j]%a[i]) return 0;
    int p=max(a[i]/a[j],a[j]/a[i]);
    for (int k=1;k<=prime[0];k++)
    if (prime[k]>=p) break;
    else if (p%prime[k]==0) return 0;
    return 1;
}
bool spfa()
{
    memset(dis,127,sizeof(dis));
    memset(up,0,sizeof(up));
    dis[s]=0;
    vis[s]=1;
    q.push(s);
    bool yes=0;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for (int i=first[x];i;i=e[i].next)
        if (e[i].w&&dis[e[i].v]>dis[x]+e[i].cost)
        {
            dis[e[i].v]=dis[x]+e[i].cost;
            up[e[i].v]=i;
            if (!vis[e[i].v]) vis[e[i].v]=1,q.push(e[i].v);
            if (e[i].v==t) yes=1;
        }
    }
    if (!yes) return 0;
    else return 1;
}
bool flow()
{
    int minn=Inf;
    for (int i=up[t];i;i=up[e[i].u])
        minn=min(minn,e[i].w);
    if (maxn+dis[t]*minn<=0)
    {
        for (int i=up[t];i;i=up[e[i].u])
            e[i].w-=minn,
            e[i^1].w+=minn;
        ans+=minn;
        maxn+=dis[t]*minn;
        return 1;
    }
    else {ans-=(maxn/dis[t]);return 0;}
}
main()
{
    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("%lld",&c[i]);
    init();
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=n;i++)
    {
        int k,sum=0;
        for (int j=1;j<=prime[0];j++)
        {
            k=a[i];
            while (k%prime[j]==0) k/=prime[j],sum++;
        }
        if (sum&1) One[++One[0]]=i;
        else Two[++Two[0]]=i;
    }
    for (int i=1;i<=One[0];i++)
        for (int j=1;j<=Two[0];j++)
        if (pd(One[i],Two[j]))
            add(One[i],Two[j],Inf,-c[One[i]]*c[Two[j]]),
            add(Two[j],One[i],0,c[One[i]]*c[Two[j]]);
    s=n+1;t=n+2;
    for (int i=1;i<=One[0];i++)
        add(s,One[i],b[One[i]],0),
        add(One[i],s,0,0);
    for (int i=1;i<=Two[0];i++)
        add(Two[i],t,b[Two[i]],0),
        add(t,Two[i],0,0);
    while (spfa()&&flow());
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值