[ 费用流 ] [ SDOI2016 ] BZOJ4514

因为只有奇数个质因子和偶数个质因子间可以匹配,所以如果在可以配对的点间连边,得到的图是二分图。
然后跑费用流直到价值总和小于 0 <script type="math/tex" id="MathJax-Element-19">0</script> 就好了。注意判断临界情况。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=210;
const int M=21000;
const int MX=64000;
const int INF=1e9;
int a[N],b[N],s[N];
int k,n,m;
int h[N],nx[M],t[M],c[M],f[M],num=1;
ll w[M],d[M];
int mn[N];
int l,r,q[M],p[N];
int Ans;
ll Sum;
bool g[N],v[N];
int pri[100000],cnt;
bool pd[MX];
inline bool Get(int x){
    bool b=0;
    for(int i=2;i*i<=x;i++)
    while(!(x%i))x/=i,b^=1;
    if(x>1)b^=1;
    return b;
}
inline bool cc(int x){
    if(x==1)return 0;
    for(int i=1;i<=cnt;i++){
        if(pri[i]*pri[i]>x)break;
        if(!(x%pri[i]))return 0;
    }
    return 1;
}
inline bool Check(int x,int y){
    if(!x||!y)return 0;
    if(x>y)swap(x,y);
    return (!(y%x)&&cc(y/x));
}
inline void Add(int x,int y,int z,ll k){
    t[++num]=y;f[num]=x;c[num]=z;w[num]=k;nx[num]=h[x];h[x]=num;
    t[++num]=x;f[num]=y;c[num]=0;w[num]=-k;nx[num]=h[y];h[y]=num;
}
inline bool Spfa(){
    memset(v,0,sizeof(v));memset(p,0,sizeof(p));
    v[0]=1;
    for(int i=1;i<=n+1;i++)d[i]=-1e18;
    l=0;q[r=1]=0;mn[0]=INF;
    while(++l<=r){
        int x=q[l];
        for(int i=h[x];i;i=nx[i])
        if(c[i]&&d[t[i]]<d[x]+w[i]){
            d[t[i]]=d[x]+w[i];p[t[i]]=i;mn[t[i]]=min(mn[x],c[i]);
            if(!v[t[i]]){
                q[++r]=t[i];
                v[t[i]]=1;
            }
        }
        v[x]=0;
    }
    if(!p[n+1])return 0;
    int fl=mn[n+1];
    if(Sum+d[n+1]*fl<0){
        Ans-=Sum/d[n+1];
        return 0;
    }
    Sum+=d[n+1]*fl;Ans+=fl;
    for(int i=n+1;i;i=f[p[i]])c[p[i]]-=fl,c[p[i]^1]+=fl;
    return 1;
}
inline void Init(){
    for(int i=2;i<MX;i++){
        if(!pd[i])pri[++cnt]=i;
        int t;
        for(int j=1;j<=cnt&&(t=i*pri[j])<MX;j++){
            pd[t]=1;
            if(!(i%pri[j]))break;
        }
    }
}
int main(){
    scanf("%d",&n);
    Init();
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),g[i]=Get(a[i]);
    for(int i=1;i<=n;i++)scanf("%d",&b[i]);
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    for(int i=1;i<=n;i++)
    if(g[i])
    for(int j=1;j<=n;j++)
    if(!g[j]&&Check(a[i],a[j]))Add(i,j,INF,1ll*s[i]*s[j]);
    for(int i=1;i<=n;i++)
    if(g[i])Add(0,i,b[i],0);else Add(i,n+1,b[i],0);
    while(Spfa());
    cout<<Ans<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值