1018 - 网络流最大匹配&神建图 - 卡牌配对(BZOJ 4205)

卡牌配对

「问题描述」

现在有一种卡牌游戏,每张卡牌上有三个属性值:A,B,C。把卡牌分为X,Y两类,分别有n1,n2张。

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

比如一张X类卡牌属性值分别是225,233,101,一张Y类卡牌属性值分别为115,466,99。那么这两张牌是可以配对的,因为只有101和99一组属性互质。

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

「输入」

数据第一行两个数n1,n2,空格分割。

接下来n1行,每行3个数,依次表示每张X类卡牌的3项属性值。

接下来n2行,每行3个数,依次表示每张Y类卡牌的3项属性值。

「输出」

输出一个整数:最多能够匹配的数目。

「样例输入」

2 2

2 2 2

2 5 5

2 2 5

5 5 5

「样例输出」

2

「提示」

样例中第一张X类卡牌和第一张Y类卡牌能配对,第二张X类卡牌和两张Y类卡牌都能配对。所以最佳方案是第一张X和第一张Y配对,第二张X和第二张Y配对。

另外,请大胆使用渐进复杂度较高的算法!

「数据规模与约定」

对于10%的数据,n1,n2≤ 10;

对于50%的数据,n1,n2≤ 3000。

对于100%的数据,n1,n2≤ 30000,属性值为不超过200的正整数

 

分析

这个建图可以有

首先n^2建图,当然是不可过的

那怎么办呢?我们思考题目中说“至多有一项属性值互质”--->“至少有两项不互质”-->至少有两项属性值分别含有相同的质因数

由于质因数的大小<200,个数也就只有46个,是个突破口

 

考虑属性值(a,b,c)总共会出现三种情况:ab,ac,bc(ab表示X类中的a属性,b属性和Y类中的a属性,b属性不互质)

我们在中间建一排点,对于第一类ab(其余类以此类推),考虑 A 项属性值能被 x 整除且 B 项属性值能被 y 整除的所有点(x,y),只要是在这个点连接的两侧一定能够匹配,所以我们在匹配的网络流模型中间增加一排这样的点,满足要求的左右点分别与它相连,边权为正无穷

 

听说匈牙利只能过10分???哈,还是网络流优秀

牢骚

本来是打算自己好好琢磨琢磨,结果巨神zzh跑过来说这道题建图很妙……一听,我就觉得自己不可做了,然后就……看题解了

但大体思路方向应该还是对了一些的,都是从其属性值下手(<=200)

 

代码

#include<bits/stdc++.h>
#define P 205
#define in read()
#define N 80009//pay attention to the 范围……
#define M 4000000
#define inf (1ll<<31)-1
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
char xxx;
bool mark[P];
int pri[P],num=0;
int n,m,S,T;
struct node{
	int x,y,z;
}a[N],b[N];
vector<int > g[205];
int nxt[M],head[N],to[M],cap[M],id[205][205];
int lev[N],cur[N];
char yyy;
inline void init(){
	int i,j;
	mark[1]=1;
	for(i=2;i<=200;++i){
		if(!mark[i]) pri[++num]=i;
		for(j=1;j<=num&&pri[j]*i<=200;++j){
			mark[pri[j]*i]=1;
			if(i%pri[j]==0) break;
		}
	}
	for(i=2;i<=200;++i){
		for(j=1;j<=num;++j)
			if(!(i%pri[j])) g[i].push_back(j);
	}
	int tot=0;
	for(i=1;i<=num;++i)
		for(j=1;j<=num;++j)
			id[i][j]=++tot;
}
int cnt=1;
inline void add(int x,int y,int z){
	nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=z;
	nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;
}
inline void link(int pos,int type){
	int x,y,z;
	if(!type) x=a[pos].x,y=a[pos].y,z=a[pos].z;
	else x=b[pos].x,y=b[pos].y,z=b[pos].z;
	int i,j,k;
	for(i=0;i<g[x].size();++i)
		for(j=0;j<g[y].size();++j)
		{
			if(!type) add(pos,id[g[x][i]][g[y][j]]+n+m,1);
			else add(id[g[x][i]][g[y][j]]+n+m,pos+n,1);
		}	
	for(i=0;i<g[x].size();++i)
		for(j=0;j<g[z].size();++j)
		{
			if(!type) add(pos,id[g[x][i]][g[z][j]]+n+m+46*46,1);
			else add(id[g[x][i]][g[z][j]]+n+m+46*46,pos+n,1);
		}	
	for(i=0;i<g[y].size();++i)
		for(j=0;j<g[z].size();++j)
		{
			if(!type) add(pos,id[g[y][i]][g[z][j]]+n+m+46*46*2,1);
			else add(id[g[y][i]][g[z][j]]+n+m+46*46*2,pos+n,1);
		}	
}
inline bool bfs(){
	for(int i=S;i<=T;++i){
		lev[i]=-1;cur[i]=head[i];
	}
	queue<int > q;
	q.push(S);lev[S]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(cap[e]<=0||lev[v]!=-1) continue;
			lev[v]=lev[u]+1;
			if(v==T) return true;
			q.push(v);
		}
	}
	return false;
}
inline int dinic(int u,int flow){
	if(u==T) return flow;
	int delta,res=0;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=to[e];
		if(lev[v]>lev[u]&&cap[e]){
			delta=dinic(v,min(flow-res,cap[e]));
			if(delta){
				cap[e]-=delta;cap[e^1]+=delta;
				res+=delta;if(res==flow) return res;
			}
		}
	}
	return res;
}
int main(){
	init();
	n=in;m=in;
	S=0;T=n+m+46*46*3+1;
	int i,j,k;
	for(i=1;i<=n;++i) a[i].x=in,a[i].y=in,a[i].z=in;
	for(i=1;i<=m;++i) b[i].x=in,b[i].y=in,b[i].z=in;
	for(i=1;i<=n;++i) add(S,i,1),link(i,0);
	for(i=1;i<=m;++i) add(i+n,T,1),link(i,1);
	int maxflow=0;
	while(bfs()) maxflow+=dinic(S,inf);
	printf("%d",maxflow);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值