解题报告-卡牌配对(网络流建模)
解题报告-卡牌配对(网络流建模)
题目描述
现在有一种卡牌游戏,每张卡牌上有三个属性值:A、B、C。把卡牌分成X和Y类,分别有 \(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;
}

浙公网安备 33010602011771号