解题报告-卡牌配对(网络流建模)

解题报告-卡牌配对(网络流建模)

题目描述

现在有一种卡牌游戏,每张卡牌上有三个属性值:ABC。把卡牌分成XY类,分别有 \(n_1\)\(n_2\)

两张卡牌能够配对,当且仅当存在至多一项属性使得该两张卡牌互质 且 两张卡牌类别不同

游戏的目的是 最大化匹配的卡牌组数,每张卡牌只能用一次

输入

数据第一行两个数 \(n_1\)\(n_2\)

接下来 $ n_1 $ 行,每行三个数,依次表示每张X类卡牌的 3 属性值

接下来 $ n_2 $ 行,每行三个数,依次表示每张Y类卡牌的 3 属性值

输出

输出一个整数,表示最大匹配的数目

样例输入
2 2
2 2 2 
2 5 5
2 2 5
5 5 5
样例输出
2

数据规模

$ n_1 \(、\)n_2$ \(\leq\) 30000


做题思路

首先考虑朴素的思想,这显然是一个二分图最大匹配
枚举 每一个X类 和 每一个Y类 , 将可以配对的建边
进行 网络流建模求最大匹配
注意,不要用 匈牙利算法 , 和 弧优化dinic 相比太慢了

观察这个思想,那 O(\(n_1\)*\(n_2\))复杂度的建边是最大的瓶颈
于是考虑如何优化建边
在 朴素思想中 我们并没有充分应用 三个属性最多只有1个互质的 性质

首先转化一下,把条件化为 三对属性中最少有两对不互质
这个 条件有两个限制 “最少两对”和“不互质

我们先 考虑不互质
\(x\) 属性为例,如果有 gcd(\(x_i\),\(x_j\))>1
根据 质因数分解的性质,这个gcd肯定 可以被 确定的一系列公共质因数 整除
那么 可以将 \(x_i\) 连向它的每一个质因数,将 \(x_j\) 的每一个质因数都连向它
当 两者之间存在通路时 ,就表示 两个数存在公共质因数,推导出不互质
属性\(y\)\(z\)同理

题目中 还有一个限制 是“最少两对属性
怎么约束?
这个条件相当与 “属性\(x\)\(y\)不互质,属性\(x\)\(z\)不互质,属性\(y\)\(z\)不互质中至少有一个成立”
我们可以把 分别处理三个组合
以 属性\(x\)\(y\)为例 ,要表示 \(X_i(x,y)\)\(Y_j(x,y)\) 不互质
可以将 属性 \(x\)的每一个质因数 p 和 属性 \(y\) 的每一个质因数 q 组成 质因数点对 (p,q)
对于 X类 连 \(X_i\) \(\rightarrow\) \((p,q)\), 对于 Y类 连 \((p,q)\)\(\rightarrow\)\(Y_j\)
\(X_i\)\(Y_j\) 存在通路 \(X_i \rightarrow (p,q) \rightarrow Y_j\)
就存在表示 \(p|\gcd(\)\(X_i\)\(.x,\)\(Y_j\).\(x\)) 且 $ q | \gcd(X_i.y,Y_j.y) $ , \(X_i\)\(Y_j\)的属性\(x\)\(y\)都不互质
对于 其他的组合 同理

此题结


#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=101100;
const int M=201;
const int nP=46;

inline int read()
{
	int f=1,x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
	return f*x;
}

struct node
{
    int x,y,z;
}w[2][N];
int n[2];

// 以下为 弧优化dinic 模板

struct edge
{
    int next,to;
    int val;
}e[N<<7];
int head[N],tot;

inline void DoubleEdge(int u,int v,int f)
{
    e[tot]=(edge){ head[u],v,f };head[u]=tot++;
    e[tot]=(edge){ head[v],u,0 };head[v]=tot++;
}

int s,t,cnt;
int dep[N];
int cur[N];

inline bool GetDeep()
{
    queue<int> q;
    // memset(dep,0,sizeof(dep));
    for(int i=0;i<=cnt;i++) dep[i]=0;
    q.push(s); dep[s]=1;
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        if(u==t) return true;
        for(int i=head[u];~i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i].val>0 && !dep[v])
            {
                dep[v]=dep[u]+1;
                q.push(v);
            }
        }
    }
    return false;
}

int FindFlow(int u,int flow)
{
    if(u==t) return flow;

    int F=0;
    for(int &i=cur[u];~i&&F<flow;i=e[i].next)
    // 不要忘了 “F<flow” 的减枝!!!
    // 因为这个我调了好久......
    {
        int v=e[i].to;
        if(e[i].val>0 && dep[v]==dep[u]+1)
        {
            int w=FindFlow(v,min(flow-F,e[i].val));
            F+=w;
            e[i].val-=w;
            e[i^1].val+=w;
        }
    }
    return F;
}

inline int Dinic()
{
    int x,ans=0;
    while(GetDeep())
    {
        // memcpy(cur,head,sizeof(head));
        for(int i=0;i<=cnt;i++) cur[i]=head[i];
        while( (x=FindFlow(s,INF)) )
          ans+=x;
    }
    return ans;
}

// 以下为 优化建图

vector<int> fr[M];  // 统计每个数的质因数
int pos[3][nP][nP];
int pr[nP]=
{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199};

inline void BuildEdge()
// 这里一定要把编号 分清!!!
// 注意x、y、z的不同组合应该有不同的质因数对编号
{
    int idx=0;
    for(int k=0;k<3;k++)
     for(int i=0;i<nP;i++)
      for(int j=0;j<nP;j++)
        pos[k][i][j]=++idx;

    // 预处理出每个数的质因数
    for(int i=2;i<M;i++)
     for(int j=0;j<nP;j++)
      if(i%pr[j]==0)
        fr[i].push_back(j);

    int tmp=n[0]+n[1]+1;
    cnt=idx+tmp+1;
    for(int i=1;i<=n[0];i++)
    {
        for(auto px:fr[w[0][i].x])
         for(auto py:fr[w[0][i].y])
          DoubleEdge(i,tmp+pos[0][px][py],1);

        for(auto px:fr[w[0][i].x])
         for(auto pz:fr[w[0][i].z])
          DoubleEdge(i,tmp+pos[1][px][pz],1);

        for(auto py:fr[w[0][i].y])
         for(auto pz:fr[w[0][i].z])
          DoubleEdge(i,tmp+pos[2][py][pz],1);
    }
    for(int i=1;i<=n[1];i++)
    {
        for(auto px:fr[w[1][i].x])
         for(auto py:fr[w[1][i].y])
          DoubleEdge(tmp+pos[0][px][py],n[0]+i,1);

        for(auto px:fr[w[1][i].x])
         for(auto pz:fr[w[1][i].z])
          DoubleEdge(tmp+pos[1][px][pz],n[0]+i,1);

        for(auto py:fr[w[1][i].y])
         for(auto pz:fr[w[1][i].z])
          DoubleEdge(tmp+pos[2][py][pz],n[0]+i,1);
    }
    s=0,t=n[0]+n[1]+1;
    for(int i=1;i<=n[0];i++) DoubleEdge(s,i,1);
    for(int i=1;i<=n[1];i++) DoubleEdge(n[0]+i,t,1);
}

signed main()
{
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
    memset(head,-1,sizeof(head));
    n[0]=read(),n[1]=read();
    for(int i=1;i<=n[0];i++) w[0][i]=(node){ read(),read(),read() };
    for(int i=1;i<=n[1];i++) w[1][i]=(node){ read(),read(),read() };
    BuildEdge();
    printf("%d",Dinic());
    return 0;
}
posted @ 2025-08-16 11:43  南北天球  阅读(17)  评论(0)    收藏  举报