【bzoj4205】卡牌配对
Solution
这题的话。。权限题,稍微说一下题面:有两组卡牌,每个卡牌有三个属性\(A,B,C\),两张牌可以配对当且仅当至多存在一组属性互质,并且两张牌所在的组不同,每张卡牌只能用一次,求最大匹配,\(0<\)属性值\(<=200\),\(n<=30000\)
然后的话。。第一眼想到网络流但是。。\(30000\)怎么想都很爆炸啊。。
所以我们需要优化建图
注意到属性值只有\(200\),然后题目那个奇奇怪怪的限制是互质,那就考虑能不能按照质因数分组(\(200\)以内的质数只有\(46\)个)
那么就有一个初步的想法:整个图分成三个部分,第一组放在左边,中间是一些素数,右边是第二组,然后现在的问题就是中间的素数要怎么设计才能满足题目的约束
注意到“最多存在一组属性互质”\(\Leftrightarrow\)“最少存在两组属性gcd\(>1\),所以我们可以考虑将匹配情况分为三种:\(A,B\)属性不互质;\(A,C\)属性不互质;\(B,C\)属性不互质
连边的话,就考虑将每个属性和它的最小质因子相连,具体一点就是:
我们记\(x\)的最小质因子为\(mnp[x]\)(不是\(mmp\)),考虑\((A_1,B_1,C_1)\)的连边情况,那应该是与中间的代表\((mnp[A_1],mnp[B_1]),(mnp[A_1],mnp[C_1]),(mnp[B_1],mnp[C_1])\)的点相连,这样我们就可以保证连向同一个中间点的不同组的数都满足有一个不为\(1\)的最小公约数
在跑最大流的时候,如果说最优匹配中两张牌有两组属性不互质,那么会直接从对应的那个中间点流过去,如果有三组属性不互质那么可能从三个中间点中的任意一个流过去,也就是说无论是哪种情况,都可以保证到能够匹配的两张牌是流到的,所以这样建图与原来暴力二分图建法是等价的
然后我们再来算一下点数和边数,点数的话就是\(2+2*n+46*46*3\)(素数两两组合\(*3\)),边数的话,因为\(2*3*5*7=210\),所以一个数至多有\(3\)个质因子,两个数能连的素数组成的点对就是\(3*3=9\)个,总的边数上界就大概是\(9*n*2+n*2\)
算一下点数最大\(66350\),边数最大\(600000\)(算上反向边的话再\(*2\)),分层图问题不大ovo
然后就很愉快滴做完啦
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=60010+46*46*46,inf=2147483647;
struct xxx{
int y,nxt,r;
}a[6000010];
struct Data{
int a,b,c;
}val[N*2];
queue<int> q;
int lv[N],h[N],mnp[210],rec1[5],rec2[5],p[50];
int vis[210];
int Id[210][210];
int n,m,S,T,tot,ans,cnt,Add;
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void add(int x,int y,int r);
void prework(int n);
bool bfs();
int dfs(int v,int o);
void dinic();
void build();
void link(int node,int x,int y,int tm);
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
tot=-1;
for (int i=1;i<=n;++i)
scanf("%d%d%d\n",&val[i].a,&val[i].b,&val[i].c);
for (int i=1;i<=m;++i)
scanf("%d%d%d\n",&val[i+n].a,&val[i+n].b,&val[i+n].c);
prework(200);
build();
dinic();
}
void prework(int n){
cnt=0;
mnp[1]=1;
for (int i=2;i<=n;++i){
if (!vis[i])
p[++cnt]=i,mnp[i]=i;
for (int j=1;j<=cnt&&p[j]*i<=n;++j){
vis[i*p[j]]=true; mnp[i*p[j]]=p[j];
if (i%p[j]==0) break;
}
}
int tmp=0;
for (int i=1;i<=cnt;++i)
for (int j=1;j<=cnt;++j)
Id[p[i]][p[j]]=++tmp;
}
void add(int x,int y,int r){
//printf("%d %d\n",x,y);
a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot; a[tot].r=r;
a[++tot].y=x; a[tot].nxt=h[y]; h[y]=tot; a[tot].r=0;
}
void dinic(){
ans=0;
while(bfs()) ans+=dfs(S,inf);
printf("%d\n",ans);
}
bool bfs(){
while (!q.empty()) q.pop();
memset(lv,0,sizeof(lv));
q.push(S);
lv[S]=1;
int v,u;
while (!q.empty()){
v=q.front(); q.pop();
for (int i=h[v];i!=-1;i=a[i].nxt){
u=a[i].y;
if (lv[u]||!a[i].r) continue;
q.push(u);
lv[u]=lv[v]+1;
if (u==T) return true;
}
}
return false;
}
int dfs(int v,int o){
if (v==T||o==0) return o;
int u,flow,ret=0;
for (int i=h[v];i!=-1;i=a[i].nxt){
u=a[i].y;
if (!a[i].r||lv[u]!=lv[v]+1) continue;
flow=dfs(u,min(a[i].r,o));
if (flow){
o-=flow;
ret+=flow;
a[i].r-=flow;
a[i^1].r+=flow;
if (!o) break;
}
}
if (ret==0) lv[v]=-1;
return ret;
}
void build(){
Add=n+m+1; S=0; T=n+m+1;
for (int i=1;i<=n;++i){
link(i,val[i].a,val[i].b,0);
link(i,val[i].a,val[i].c,1);
link(i,val[i].b,val[i].c,2);
}
for (int i=n+1;i<=n+m;++i){
link(i,val[i].a,val[i].b,0);
link(i,val[i].a,val[i].c,1);
link(i,val[i].b,val[i].c,2);
}
for (int i=1;i<=n;++i) add(S,i,1);
for (int i=1;i<=m;++i) add(i+n,T,1);
}
void div(int x,int *rec){
int tmp;
rec[0]=0;
while (x>1){
tmp=mnp[x];
rec[++rec[0]]=mnp[x];
while (x%tmp==0&&x>1) x/=tmp;
}
}
void link(int node,int x,int y,int tm){
div(x,rec1); div(y,rec2);
for (int i=1;i<=rec1[0];++i)
for (int j=1;j<=rec2[0];++j)
if (node<=n) add(node,Id[rec1[i]][rec2[j]]+Add+46*46*tm,1);
else add(Id[rec1[i]][rec2[j]]+Add+46*46*tm,node,1);
}