[BZOJ4514] [SDOI2016] 数字配对 - 费用流

显然可以根据质因子个数建二分图,再连源点和汇点,连边就可以跑费用流了。每次跑最大费用,直到费用小于0就结束过程,记录总流量即可 - -

#include"bits/stdc++.h"
using namespace std;
typedef long long ll;
 
const int N=40005,M=50000;
const int inf=(int)1e9;
const ll llinf=(ll)1e18;
 
int pri[M+5 >> 2],cnt,flag[M+5];
void Linear_Shaker(){
    for(int i=2;i<=M;i++){
        if(!flag[i])pri[++cnt]=i;
        for(int j=1;pri[j]*i<=M;j++){
            flag[pri[j]*i]=1;
            if(i%pri[j]==0)break;
        }
    }
}
 
int a[N],n,f[N],c[N],color[N],S,T;
struct Edge {
    int to;ll cost,flow;
    Edge (int _=0,ll __=0,ll ___=0)
    { to=_; flow=__; cost=___;}
} e[N << 1];
int head[N],next[N << 1],tmp=1;
 
inline void addedge(int fr,int to,ll cost,int flow){
    e[++tmp]=Edge(to,flow,cost);next[tmp]=head[fr];head[fr]=tmp;
    e[++tmp]=Edge(fr,0,-cost);next[tmp]=head[to];head[to]=tmp;
}
 
int sep(int x){
    int ans=0;if(x<2)return 0;
    for(int i=1;pri[i]*pri[i]<=x;i++)
        while(x%pri[i]==0)ans++,x/=pri[i];
    return ans+(x>1);
}
 
void init(){
    Linear_Shaker();
    scanf("%d",&n);int i,j,s,t;S=0,T=n+1;
    for(i=1;i<=n;i++)scanf("%d",&a[i]);
    for(i=1;i<=n;i++)scanf("%d",&f[i]);
    for(i=1;i<=n;i++)scanf("%d",&c[i]);
    for(i=1;i<=n;i++)color[i]=sep(a[i]);
    for(i=1;i<=n;i++)for(j=i+1;j<=n;j++){
        s=i,t=j;if(a[i]<a[j])swap(s,t);
        if(a[s]%a[t]==0&&color[s]==color[t]+1){
            s=i,t=j;if(color[i]&1)swap(s,t);
            addedge(s,t,(ll)c[i]*c[j],inf);
        }
    }
    for(i=1;i<=n;i++)
        if(color[i]&1)addedge(i,T,0,f[i]);
        else addedge(S,i,0,f[i]);
}
 
int pre[N],q[N],l,r,inq[N],id[N];
ll dist[N];
 
bool spfa(){
    for(int i=S;i<=T;i++)dist[i]=-llinf,pre[i]=id[i]=0;
    dist[S]=0;l=r=1;q[++r]=S;inq[S]=1;
    while(l!=r){
        l=(l+1)%(n+3);
        int s=q[l];inq[s]=0;
        for(int i=head[s];i;i=next[i]){
            if(e[i].flow>0){
                if(dist[s]+e[i].cost>dist[e[i].to]){
                    dist[e[i].to]=dist[s]+e[i].cost;
                    pre[e[i].to]=s;id[e[i].to]=i;
                    if(!inq[e[i].to]){
                        r=(r+1)%(n+3);
                        q[r]=e[i].to;
                        inq[e[i].to]=1;
                    }
                }
            }
        }
    }
    return pre[T]!=0;
}
 
void work(){
    ll maxflow=0,i,flow,sumcost=0;
    while(spfa()&&sumcost+dist[T]>=0){
        flow=inf;
        for(i=T;i;i=pre[i])
            flow=min(flow,e[id[i]].flow);
        if(sumcost+flow*dist[T]<0)
            flow=sumcost/(-dist[T]);
        for(i=T;i;i=pre[i]){
            e[id[i]].flow-=flow;
            e[id[i]^1].flow+=flow;
        }
        maxflow+=flow;
        sumcost+=flow*dist[T];
    }
    printf("%lld\n",maxflow);
}
 
int main(){
    init();work();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值