卡牌配对(bzoj4205)-网络流
题目描述
题解
一,暴力建边+匈牙利算法
时间复杂度 O ( n 2 ) O(n^2) O(n2),可以得六十分
代码实现
#include<bits/stdc++.h>
#define M 100009
using namespace std;
vector<int>e[M];
struct card{
int x,y,z;
}p[M],q[M];
int n,m,match[M],vis[M],ans,cnt;
int gcd(int x,int y){
if(!y) return x;
return gcd(y,x%y);
}
bool solve(int x){
for(int i=0;i<e[x].size();i++)
if(!vis[e[x][i]]){
vis[e[x][i]]=1;
if(!match[e[x][i]]||solve(match[e[x][i]])){
match[e[x][i]]=x;
return 1;
}
}return 0;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].z);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cnt=0;
if(gcd(p[i].x,q[j].x)==1) cnt++;
if(gcd(p[i].y,q[j].y)==1) cnt++;
if(gcd(p[i].z,q[j].z)==1) cnt++;
if(cnt<=1) e[i].push_back(j);
}
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
ans+=solve(i);
}printf("%d\n",ans);
return 0;
}
二,分类+网络流
观察数据范围,属性值都小于200.而200以内的质数只有40多个,所以考虑分为3类,分别是AB,AC,BC。以AB类举例子,如果一张X类卡牌的A能被质数a整除,B能被质数b整除,则由该卡牌向(a,b)连一条容量为1的边。反之如果是Y类卡牌,则由(a,b)向该卡牌连边。如果连接在同一个质数点对的X类卡牌和Y类卡牌必能匹配,最后连上源点,汇点,跑最大流即可
代码实现
#include<bits/stdc++.h>//网络流建模
#define M 1100009
using namespace std;
vector<int>pre[220];
int tot=1,nxt[M],first[M],to[M],w[M],d[M],now[M];
int n,m,sum,cnt,num[221][211],T,prime[221];
const int inf=1e9;
void add(int x,int y,int z){
nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
nxt[++tot]=first[y],first[y]=tot,to[tot]=x,w[tot]=0;
}
bool bfs(){
memset(d,0,sizeof(d));
d[0]=1,now[0]=first[0];
queue<int>q;
q.push(0);
while(q.size()){
int u=q.front();
q.pop();
for(int i=first[u];i;i=nxt[i]){
int v=to[i];
if(w[i]&&!d[v]){
d[v]=d[u]+1,now[v]=first[v];
q.push(v);
if(v==T) return 1;
}
}
}return 0;
}
int dfs(int x,int flow){
if(x==T) return flow;
int rest=flow,i;
for(i=now[x];i&&rest;i=nxt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(rest,w[i]));
w[i]-=k,w[i^1]+=k,rest-=k;
}
}now[x]=i;
return flow-rest;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(0,inf);
return ans;
}
void init(){
for(int i=2;i<=200;i++) prime[i]=1;
for(int i=2;i<=200;i++)
for(int j=2;j<=i;j++)
if(prime[j]&&i%j==0){pre[i].push_back(j);if(i!=j) prime[i]=0;}
for(int i=2;i<=200;i++)
for(int j=2;j<=200;j++)
if(prime[i]&&prime[j]) num[i][j]=++cnt;
}
int main(){
int x,y,z;
scanf("%d%d",&n,&m);
init(),sum=cnt*3,T=sum+n+m+1;
for(int i=1;i<=n;i++){
sum++;
scanf("%d%d%d",&x,&y,&z);add(0,sum,1);
for(int j=0;j<pre[x].size();j++)
for(int k=0;k<pre[y].size();k++)
add(sum,num[pre[x][j]][pre[y][k]],1);
for(int j=0;j<pre[x].size();j++)
for(int k=0;k<pre[z].size();k++)
add(sum,num[pre[x][j]][pre[z][k]]+cnt,1);
for(int j=0;j<pre[y].size();j++)
for(int k=0;k<pre[z].size();k++)
add(sum,num[pre[y][j]][pre[z][k]]+cnt*2,1);
}
for(int i=1;i<=m;i++){
sum++;
scanf("%d%d%d",&x,&y,&z);
for(int j=0;j<pre[x].size();j++)
for(int k=0;k<pre[y].size();k++)
add(num[pre[x][j]][pre[y][k]],sum,1);
for(int j=0;j<pre[x].size();j++)
for(int k=0;k<pre[z].size();k++)
add(num[pre[x][j]][pre[z][k]]+cnt,sum,1);
for(int j=0;j<pre[y].size();j++)
for(int k=0;k<pre[z].size();k++)
add(num[pre[y][j]][pre[z][k]]+cnt*2,sum,1);
add(sum,T,1);
}//printf("%d %d %d",cnt,sum,T);
//printf("%d\n",tot);
printf("%d\n",dinic());
return 0;
}
做题启发
1,善于观察数据范围,特定的数据范围往往是题目的切入点
2,善于转化问题,逆向思维非常重要