dtoi2678「SDOI2016」数字配对

题意:

     有 $ n $ 种数字,第 $ i $ 种数字是 $ a_i $、有 $ b_i $ 个,权值是 $ c_i $。

    若两个数字 $ a_i $、$ a_j $ 满足,$ a_i $ 是 $ a_j $ 的倍数,且 $ a_i / a_j $ 是一个质数,那么这两个数字可以配对,并获得 $ c_i \cdot c_j $ 的价值。
    一个数字只能参与一次配对,可以不参与配对。
    在获得的价值总和不小于 $ 0 $ 的前提下,求最多进行多少次配对。

    $ n \leq 200 $,$ a_i \leq 10 ^ 9 $,$ b_i \leq 10 ^ 5 $,$ \left| c_i \right| \leq 10 ^ 5 $。

题解:

     阅读题目,观察到“配对”,“价值”,还有“只能参与一次配对”,容易联想到费用流。但是如果直接连边,似乎无法保证每个数字只能用 $b[i]$ 次。考虑这种配对问题,普通的费用流一般是怎么建边的,就是一些放左边,一些放右边,然后左右连边。但这道题似乎没有明显的划分依据。

     先考虑把各种边连上,这样就连出来一个图(当它是无向图),想要将其划分为左右两边,相当于这张图可以 $0/1$ 染色,即这张图是二分图。那这张图是不是二分图?可以发现,对于图中任意一个环,也就是从一个点出发回到自己本身,每走一步必然是质因子次数总和变化 $1$,那么必然是走了偶数步。换句话说,这张图所有的环,都是偶环,那么就是二分图了。

     然后问题就很简单了,二分图染色一下,$0$ 放左边,$1$ 放右边,连边完二分+费用流即可。

#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cstdlib>
using namespace std;
const long long INF=1e18;
int n,aa[202],b[202],c[202],p[205],s=201,s2=202,t=203,ys[202];
long long d[205],a[205];
bool fl[205];
struct Edge{
    int from,to;
    long long cap,flow,cost;
    Edge(int u,int v,long long c,long long f,long long w):from(u),to(v),cap(c),flow(f),cost(w){}
};
vector<Edge>edges;
vector<int>g[205],y[205];
void init(){
    for (int i=1;i<=t;i++)g[i].clear();
    edges.clear();
}
void add(int from,int to,long long cap,long long cost){
    edges.push_back(Edge(from,to,cap,0,cost));
    edges.push_back(Edge(to,from,0,0,-cost));
    g[from].push_back(edges.size()-2);
    g[to].push_back(edges.size()-1); 
}
bool spfa(int s,int t,long long& flow,long long& cost){
    for (int i=1;i<=t;i++)d[i]=-INF;
    d[s]=0;fl[s]=1;a[s]=INF;
    queue<int>q;q.push(s);memset(fl,0,sizeof(fl));
    while(!q.empty())
    {
        int u=q.front();q.pop();
        fl[u]=0;
        for (int i=0;i<g[u].size();i++)
        {
            Edge e=edges[g[u][i]];
            if (e.cap>e.flow && d[u]+e.cost>d[e.to])
            {
                d[e.to]=d[u]+e.cost;
                p[e.to]=g[u][i];
                a[e.to]=min(a[u],e.cap-e.flow);
                if (!fl[e.to]){q.push(e.to);fl[e.to]=1;}
            }
        }
    }
    if (d[t]==-INF)return false;
    flow+=a[t];cost+=d[t]*a[t];
    for (int u=t;u!=s;u=edges[p[u]].from)
    {
        edges[p[u]].flow+=a[t];edges[p[u]^1].flow-=a[t];
    }
    return true;
}
void dfs(int x){
    for (int i=0;i<y[x].size();i++)
    if (ys[y[x][i]]==-1)
    {
        ys[y[x][i]]=(ys[x]^1);dfs(y[x][i]);
    }
}
bool pd(long long x){
    init();add(s,s2,x,0);
    for (int i=1;i<=n;i++)
    if (ys[i]==0)
    {
        add(s2,i,b[i],0);
        for (int j=0;j<y[i].size();j++)add(i,y[i][j],INF,(long long)c[i]*c[y[i][j]]);
    }
    else add(i,t,b[i],0);
    long long flow=0,cost=0;
    while(spfa(s,t,flow,cost));
    return (flow==x && cost>=0);
}
bool isprim(int x){
    for (int i=2;i*i<=x;i++)
    if (x%i==0)return false;
    return true;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",&aa[i]);
    for (int i=1;i<=n;i++)scanf("%d",&b[i]);
    for (int i=1;i<=n;i++)scanf("%d",&c[i]);
    for (int i=1;i<=n;i++)
    for (int j=i+1;j<=n;j++)
    if (aa[i]%aa[j]==0 && isprim(aa[i]/aa[j]) || aa[j]%aa[i]==0 && isprim(aa[j]/aa[i]))
    {
        y[i].push_back(j);y[j].push_back(i);
    }
    memset(ys,-1,sizeof(ys));
    for (int i=1;i<=n;i++)if (ys[i]==-1){ys[i]=0;dfs(i);}
    long long lef=0,righ=1000000000,mid;
    while(lef<righ)
    {
        mid=(lef+righ+1)/2;
        if (pd(mid))lef=mid;else righ=mid-1;
    }
    printf("%lld\n",lef);
    return 0;
}
posted @ 2021-02-16 17:02  1124828077ccj  阅读(54)  评论(0编辑  收藏  举报