费用流 【SDOI2016】 bzoj4514 数字配对

题目大意:
有 n 种数字,第 i 种数字是 ai、有 bi 个,权值是 ci。
若两个数字 ai、aj 满足,ai 是 aj 的倍数,且 ai/aj 是一个质数,
那么这两个数字可以配对,并获得 ci×cj 的价值。
一个数字只能参与一次配对,可以不参与配对。
在获得的价值总和不小于 0 的前提下,求最多进行多少次配对。
(题目很清晰啊,就不转述了)

题目分析:
如果一个数ai能整除aj并且商是一个质数,那么对ai和aj分解质因数,ai和aj的质因子的个数一定差1,一定是一奇一偶的。也就是说质因子个数同奇同偶的两个数不能配对。
所以就是说这些点可以构成一个二分图,一侧是质因子个数为奇的,另一侧是质因子个数为偶的。

这样我们可以从源点向左侧的点连一条容量为bi,费用为0的边;从右侧的点到汇点连一条容量为bi费用为0的边,代表第i个点只能选择bi次。
然后在暴力枚举左侧的点和右侧的点,把可以配对的两个点连一条容量为INF,费用为ci*cj的边。代表配对次数不限,但是会获得 ci * cj的价值。

题目要求总价值和不小于0,就跑最大费用最大流。
因为我们每次做spfa都是在有可行流的前提下找到价值最高的流,那么每次获得的价值就是单调不减的,那只要跑到总价值小于0之前就跳出即可。

注意事项:
1、判质数一定不要用你筛质数时候用的那个数组去判断,因为两数相除得出的数会爆出你的质数表(一定要让我把这段痛苦的经历说出来啊啊啊,我居然在判断质数这个地方卡了三天,我把我的筛素数,建图,spfa,费用流的所有部分都检查了若干遍,结果居然是判素数的时候出错了QAQ,一定要好好判素数);
2、容量和价值用long long类型来存,价值相乘会爆int的;
3、小技巧:INF可以赋值成 -0x8f8f8f8f8f8f8f8f ,这样你就在spfa里就可以memset(f,0x8f,sizeof(f)) 来赋最小值了,这样做的最小值就是-INF(虽然没什么太大用,但是代码会显得很好看……吧大概)。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<queue>
#define N 300000
using namespace std;
const long long INF=-0x8f8f8f8f8f8f8f8fll;
int n,S,T;
long long ans;
int pri[31634],tot;
bool e[31634],pc[N];
int a[N],d[N];
long long b[N],c[N];
int fir[N],nes[N],v[N],top=1,bak[N],road[N];
long long q[N],w[N],f[N];
bool prime(int x)
{
    for(int i=1;i<=tot;i++)
        if(x==pri[i]) return true;
        else if(x%pri[i]==0) return false;

    return true;
}
void edge(int x,int y,long long z,long long p)
{
    top++;
    bak[top]=x; v[top]=y;
    q[top]=z; w[top]=p;
    nes[top]=fir[x];fir[x]=top;
    return;
}
#define edge(x,y,z,p) edge(x,y,z,p),edge(y,x,0,-p)
bool spfa()
{
    queue<int> V;
    memset(f,0x8f,sizeof(f));
    V.push(S); f[S]=0; pc[S]=true;
    while(!V.empty())
    {
        int c=V.front();
        for(int t=fir[c];t;t=nes[t])
        {
            if(!q[t] || f[v[t]]>=f[c]+w[t]) continue;
            f[v[t]]=f[c]+w[t]; road[v[t]]=t;
            if(pc[v[t]]) continue;
            V.push(v[t]); pc[v[t]]=true;
        }
        V.pop(); pc[c]=false;
    }
    return f[T]!=-INF;
}
void fly()
{
    long long cs=0;
    while(spfa())
    {
        long long c=INF;
        for(int t=T;t!=S;t=bak[road[t]]) c=min(c,q[road[t]]);
        if(f[T]*c+cs>=0) {ans+=c; cs+=f[T]*c;}
        else {ans+=cs/(-f[T]); break;}
        for(int t=T;t!=S;t=bak[road[t]])
        {
            q[road[t]]-=c;
            q[road[t]^1]+=c;
        }
    }
    return;
}
int main()
{
    scanf("%d",&n);
    S=n+1; T=S+1;
    for(int i=2;i<=31633;i++) if(!e[i])
        for(int j=i+i;j<=31633;j+=i) e[j]=true;
    for(int i=2;i<=31633;i++) if(!e[i]) pri[++tot]=i;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        int t=a[i],sum=0,radical=sqrt(a[i]);
        for(int j=1;j<=tot;j++)
        {
            while(t%pri[j]==0) sum++,t/=pri[j];
            if(t==1) break;
            if(pri[j]>radical) break;
        }
        if(t!=1) sum++;
        d[i]=sum%2;
    }
    for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
    for(int i=1;i<=n;i++)
    {
        if(d[i])  edge(S,i,b[i],0);
        else     {edge(i,T,b[i],0); continue;}
        for(int j=1;j<=n;j++)
        {
            if(d[j]) continue;
            int x=a[i],y=a[j];
            if(x<y) swap(x,y);
            if(x%y!=0) continue;
            if(prime(x/y)) edge(i,j,INF,c[i]*c[j]);
        }
    }
    fly();
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值