【bzoj 4205】卡牌配对(最大流)

传送门biu~
最暴力的做法是构建二分图,也就是暴力枚举X和Y中的两张卡牌能否配对然后连边跑最大匹配。
如果想优化这个算法,可以考虑在二分图左右之间再建一排点,使得左右能匹配的两张卡牌可以连向同一个点。设有三类节点 (a,b),(a,c),(b,c) 代表 X Y的这两项不互质,即可以满足“至多一项属性值互质”的条件。以 (a,b) 类型的节点为例,点 (p,q) 表示 a 属性值能整除第p个质数, b 属性值能整除第q个质数,如果 X 中的某卡牌满足这个条件,就让这个卡牌向(p,q)连一条流量为 1 的弧;如果Y中某卡牌满足这个条件,就让 (p,q) 向这个卡牌连一条流量为 1 的弧。(a,c) (b,c) 类型的节点同理。也就是说,当 X 中的一张卡牌和Y中的一张卡牌满足“至多一项属性值互质”的条件时,一定会通过这两张卡牌间的某点连接起来。可以发现中间的点最多有 34646 个。又因为 200<2357 ,因此每张卡牌最多向中间点连 3 条边。因为一张卡牌只能匹配一次,所以S X 以及Y T 1限流。
于是一个有70000个点,4000000条弧的网络流图诞生了。因为是分层图,所以Dinic的时间复杂度十分优秀。

#include<bits/stdc++.h>
using namespace std;
const int INF=1e9;
int n1,n2,S,T;
struct Card{int a,b,c;}x[30005],y[30005];
int _head[205],_nex[40005],_to[40005],_tp;
inline void _add(int x,int y){
    _nex[++_tp]=_head[x];
    _head[x]=_tp;
    _to[_tp]=y;
}
int prime[205];bool b[205];
inline void getprime(){
    for(int i=2;i<=200;++i){
        if(!b[i]) prime[++prime[0]]=i;
        for(int j=1;j<=prime[0] && i*prime[j]<=200;++j){
            b[i*prime[j]]=true;
            if(i%prime[j]==0)       break;
        }
    }
    for(int i=2;i<=200;++i)
        for(int j=1;j<=prime[0];++j)
            if(i%prime[j]==0) _add(i,j);
}
int fir[70005],head[70005],dep[70005],nex[4000005],to[4000005],cap[4000005],tp=1,prm[50][50];
inline void add(int x,int y,int c){
    nex[++tp]=head[x];
    head[x]=tp;
    to[tp]=y;
    cap[tp]=c;
}
inline void Insert(int x,int y){add(x,y,1);add(y,x,0);}
inline void build1(int p){
    for(int i=_head[x[p].a];i;i=_nex[i])
        for(int j=_head[x[p].b];j;j=_nex[j])
            Insert(p,n1+n2+prm[_to[i]][_to[j]]);
    for(int i=_head[x[p].a];i;i=_nex[i])
        for(int j=_head[x[p].c];j;j=_nex[j])
            Insert(p,n1+n2+46*46+prm[_to[i]][_to[j]]);
    for(int i=_head[x[p].b];i;i=_nex[i])
        for(int j=_head[x[p].c];j;j=_nex[j])
            Insert(p,n1+n2+46*46*2+prm[_to[i]][_to[j]]);
}
inline void build2(int p){
    for(int i=_head[y[p].a];i;i=_nex[i])
        for(int j=_head[y[p].b];j;j=_nex[j])
            Insert(n1+n2+prm[_to[i]][_to[j]],n1+p);
    for(int i=_head[y[p].a];i;i=_nex[i])
        for(int j=_head[y[p].c];j;j=_nex[j])
            Insert(n1+n2+46*46+prm[_to[i]][_to[j]],n1+p);
    for(int i=_head[y[p].b];i;i=_nex[i])
        for(int j=_head[y[p].c];j;j=_nex[j])
            Insert(n1+n2+46*46*2+prm[_to[i]][_to[j]],n1+p);
}
inline void solve(){
    int num=0;
    for(int i=1;i<=46;++i)
        for(int j=1;j<=46;++j)
            prm[i][j]=++num;
    S=n1+n2+3*46*46+1;T=n1+n2+3*46*46+2;
    for(int i=1;i<=n1;++i)
        Insert(S,i),build1(i);
    for(int i=1;i<=n2;++i)
        Insert(n1+i,T),build2(i);
}
inline int bfs(){
    memset(dep,0,sizeof(dep));
    dep[S]=1;queue<int>q;
    q.push(S);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=nex[i]){
            if(cap[i] && !dep[to[i]]){
                dep[to[i]]=dep[x]+1;
                q.push(to[i]);
            }
        }
    }
    return dep[T];
}
int dfs(int x,int now){
    if(x==T || !now)    return now;
    int c=0;
    for(int &i=fir[x];i;i=nex[i]){
        if(dep[to[i]]==dep[x]+1 && cap[i]){
            int f=dfs(to[i],min(now,cap[i]));
            now-=f;
            cap[i]-=f;
            cap[i^1]+=f;
            c+=f;
            if(!now)    break;
        }
    }
    return c;
}
inline int Dinic(){
    int c=0;
    while(bfs()){
        for(int i=1;i<=n1+n2+3*46*46+2;++i) fir[i]=head[i];
        c+=dfs(S,INF);
    }
    return c;
}
int main(){
    scanf("%d%d",&n1,&n2);
    for(int i=1;i<=n1;++i)  scanf("%d%d%d",&x[i].a,&x[i].b,&x[i].c);
    for(int i=1;i<=n2;++i)  scanf("%d%d%d",&y[i].a,&y[i].b,&y[i].c);
    getprime();solve();
    printf("%d",Dinic());
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zP1nG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值