[bzoj4514][SDOI2016]数字配对

题目大意

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

二分图!

我们来考虑配对条件:
如果记f(x)表示x分解质因数后的项数(注意4=2*2所以f(4)=2,也就是说这里指的是分成若干项每一项均为质数的项数)
那么x和y可以配对的条件:f(x)=f(y)+1且x是y的倍数。
易证。
那么如果可以配对进行连边的。
我们把f值为奇数的放一边,f值为偶数的放一边,出来的是二分图!

网络流

源向i/i向汇连容量为bi费用为0的边。
i、j可以匹配,i向j连容量为无穷费用为ci*cj的边。
接下来,我们来描述任务:
分配流量使得每条边的流量不大于容量,且获得的费用和不小于0,求最大流。
我们来贪心!
每次沿着费用最长路增广。
如果当前源与汇不连通或者即使往最长路增广1的流量都会使总费用小于0就退出。
假设当前最长路为t,可以增广l的流量。
如果该增广路费用和为负数,那么需要注意保证总费用小于0,详见代码。
然后增广就是了。
(第一次用spfa写费用流

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<deque>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll maxn=200+10,maxm=50000+10,maxd=1000000000,maxdd=1000000,inf=100000000000000000;
ll h[maxn],go[maxm*2],dis[maxm*2],cost[maxm*2],fx[maxm*2],next[maxm*2];
ll a[maxn],b[maxn],c[maxn],col[maxn];
ll d[maxn],rl[maxn],pre[maxn];
deque<ll> dl;
ll pri[maxdd];
bool bz[maxdd];
ll i,j,k,l,r,s,t,n,m,tot,top,ans,now;
void add(ll x,ll y,ll z,ll c,ll d){
    go[++tot]=y;
    dis[tot]=z;
    cost[tot]=c;
    fx[tot]=tot+d;
    next[tot]=h[x];
    h[x]=tot;
}
void spfa(){
    fo(i,s,t) d[i]=-inf;
    fo(i,s,t) bz[i]=0;
    d[s]=0;
    rl[s]=inf;
    dl.push_back(s);
    bz[s]=1;
    while (!dl.empty()){
        j=dl.front();
        r=h[j];
        while (r){
            if (dis[r]&&d[j]+cost[r]>d[go[r]]){
                d[go[r]]=d[j]+cost[r];
                pre[go[r]]=r;
                rl[go[r]]=min(rl[j],dis[r]);
                if (!bz[go[r]]){
                    bz[go[r]]=1;
                    dl.push_back(go[r]);
                }
            }
            r=next[r];
        }
        dl.pop_front();
        bz[j]=0;
    }
}
int main(){
    freopen("pair.in","r",stdin);freopen("pair.out","w",stdout);
    fo(i,2,floor(sqrt(maxd))){
        if (!bz[i]) pri[++top]=i;
        fo(j,1,top){
            if (i*pri[j]>floor(sqrt(maxd))) break;
            bz[i*pri[j]]=1;
            if (i%pri[j]==0) break;
        }
    }
    scanf("%lld",&n);
    fo(i,1,n){
        scanf("%lld",&a[i]);
        k=a[i];
        t=0;
        fo(j,1,top)
            while (k%pri[j]==0){
                t++;
                k/=pri[j];
            }
        col[i]=t;
    }
    fo(i,1,n) scanf("%lld",&b[i]);
    fo(i,1,n) scanf("%lld",&c[i]);
    fo(i,1,n)
        if (col[i]%2)
            fo(j,1,n)
                if (col[j]==col[i]-1&&a[i]%a[j]==0||col[j]==col[i]+1&&a[j]%a[i]==0){
                    add(i+1,j+1,inf,c[i]*c[j],1);
                    add(j+1,i+1,0,-c[i]*c[j],-1);
                }
    s=1;t=n+2;
    fo(i,1,n){
        if (col[i]%2){
            add(s,i+1,b[i],0,1);
            add(i+1,s,0,0,-1);
        }
        else{
            add(i+1,t,b[i],0,1);
            add(t,i+1,0,0,-1);
        }
    }
    while (1){
        spfa();
        if (d[t]==-inf||now+d[t]<0) break;
        if (d[t]>=0) l=rl[t];else l=min(rl[t],now/(-d[t]));
        ans+=l;
        now+=d[t]*l;
        j=t;
        while (j!=s){
            dis[pre[j]]-=l;
            dis[fx[pre[j]]]+=l;
            j=go[fx[pre[j]]];
        }
    }
    printf("%lld\n",ans);
}

你可能感兴趣的:([bzoj4514][SDOI2016]数字配对)