二分图习题总结
二分图:把一个图的顶点划分为两个不相交集 U
和V
,使得每一条边都分别连接U
、V
中的顶点。如果存在这样的划分,则此图为一个二分图。
二分图一般可以处理一些冲突问题,算法不难,但建边有的题会比较复杂。
类型一:逃离问题
描述:有一些人在一个房间内,房间内有一些门,突然发生了紧急情况,问所有人都逃离所需要的最小时间。
如果更详细地划分的话,还可以分为门消失和门不消失两种。
1.1 Way Selection
题目描述
小杉家族r
个人正在一片空地上散步,突然,外星人来了……
留给小杉家族脱逃的时间只有t
秒,每个小杉都有一个跑的速度v
,总共有a
个传送点,小杉们必须在t
秒内到达传送点才能脱逃。另外一个小杉进入一个传送点以后,该传送点就会消失。
现在请你安排一种方案,使脱逃的小杉尽可能的多。
输入格式
每组测试数据的
第一行有三个整数r
和a
和`t(0
第二行有a
对实数,第i
对数表示第i
个传送点的坐标,这些坐标绝对值均不超过1e6
。
接下来r
行,每行有三个实数x,y,v
,表示第i
个小杉的坐标和奔跑的速度
输出格式
对每组测试数据输出一行
仅有一个整数s
表示最多有多少个小杉能成功脱逃
样例
样例输入
1 1 1
1 1
1 1 1
样例输出
1
分析
利用两点间距离公式预处理处每个人到门的最短距离,如果在规定时间内能走这么多距离,就把人和门建边,最后再跑一个匈牙利就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
typedef double dd;
vector<int> g[maxn];
int vis[maxn],pp[maxn];
int jl[maxn];
int r,a;
dd t;
dd jlx[maxn],jly[maxn];
dd pf(dd aa){
return aa*aa;
}
int jud(dd aa,dd bb,dd cc,dd ee,dd vv){
dd jl=(double)sqrt(pf(fabs(aa-cc))+pf(fabs(bb-ee)));
dd ans=vv*(dd)t;
if(ans>=jl) return 1;
else return 0;
}
int dfs(int xx){
int tot=g[xx].size();
for(int i=0;i<tot;i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(!pp[u] || dfs(pp[u])){
pp[u]=xx;
jl[xx]=u;
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d%d%lf",&r,&a,&t);
for(int i=1;i<=a;i++){
dd aa,bb;
scanf("%lf%lf",&aa,&bb);
jlx[i]=aa,jly[i]=bb;
}
int ans=0;
for(int i=1;i<=r;i++){
dd aa,bb,cc;
scanf("%lf%lf%lf",&aa,&bb,&cc);
for(int j=1;j<=a;j++){
if(jud(jlx[j],jly[j],aa,bb,cc)) g[i].push_back(j);
}
}
for(int i=1;i<=r;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d
",ans);
return 0;
}
1.2 POJ3057 Evacuation
题目描述
Fires can be disastrous, especially when a fire breaks out in a room that is completely filled with people. Rooms usually have a couple of exits and emergency exits, but with everyone rushing out at the same time, it may take a while for everyone to escape.
You are given the floorplan of a room and must find out how much time it will take for everyone to get out. Rooms consist of obstacles and walls, which are represented on the map by an 'X', empty squares, represented by a '.' and exit doors, which are represented by a 'D'. The boundary of the room consists only of doors and walls, and there are no doors inside the room. The interior of the room contains at least one empty square.
Initially, there is one person on every empty square in the room and these persons should move to a door to exit. They can move one square per second to the North, South, East or West. While evacuating, multiple persons can be on a single square. The doors are narrow, however, and only one person can leave through a door per second.
What is the minimal time necessary to evacuate everybody? A person is evacuated at the moment he or she enters a door square.
Input
The first line of the input contains a single number: the number of test cases to follow. Each test case has the following format:
One line with two integers Y and X, separated by a single space, satisfying 3 <= Y, X <= 12: the size of the room
Y lines with X characters, each character being either 'X', '.', or 'D': a valid description of a room
Output
For every test case in the input, the output should contain a single line with the minimal evacuation time in seconds, if evacuation is possible, or "impossible", if it is not.
Sample Input
3
5 5
XXDXX
X...X
D...X
X...D
XXXXX
5 12
XXXXXXXXXXXX
X..........D
X.XXXXXXXXXX
X..........X
XXXXXXXXXXXX
5 5
XDXXX
X.X.D
XX.XX
D.X.X
XXXDX
Sample Output
3
21
impossible
分析
一句话题意:有一个X( imes)Y的房间,'X'代表墙壁,‘D’是门,‘.’代表人。这个房间着火了,人要跑出去,但是每一个时间点只有一个人可以从门出去。
问最后一个人逃出去的最短时间,如果不能逃出去,输出impossible。
其实这一道题和题库里外星人那一道题解法大致雷同,只是建边的方式有点不同,我们对比着来说
首先,外星人那一道题就是一个简单的两点之间距离公式求出最短路
但是这一道题肯定是不可以直接用公式的,因为题中会有墙
所以我们要用一个bfs预处理出每一个人到每一扇门的最短路
还有一个更大的不同点就是门的性质
在外星人那一道题中,一个人经过了一扇门后,门就会消失,而这一道题经过一扇门后,门不会消失
所以一扇门可以使用多次,但不能被多个人同时使用
所以我们考虑把一个门拆成若干个小门,每一个门代表一个时刻
我们从0时刻开始枚举,把0时刻每一个人可以到达的门和这个人连一条边
如果0时刻不能匹配成功,我们就把1时刻每一个人可以到达的门和这个人连一条边
以此类推,直到所有的时刻都枚举完毕
那么最大的时刻是多少呢
很显然,一个人如果可以从门里走出去的话,那么他到这个门所花费的时间不会超过(n imes m)
其中(m)为房间的长度,(n)为房间的宽度
我们假设题目中共有(d)个门,因为每一个门都要拆分到(n imes m)个时间段里,所以我们只要枚举到(n imes m imes d)就可以了
如果最后不能匹配成功,人就不能逃生成功
感性地理解一下,就是一个人被一圈墙围在中间
最后,人数为0的情况下要特判,不特判就会输出'impossible'
最后,我们再来讨论一下复杂度的问题
bfs最坏的情况下就是(12 imes12 imes (12 imes12+12 imes24)=62208)
没什么影响
建边更是可以忽略不计
其实最主要的开销还是在匈牙利上
最坏的情况(144)个点,248832条边,总的复杂度为(35831808)
但是这样的强度是达不到的,因为你不可能门有144个,人也有144个,所以实际上是可行的
这道题有人可能会想到二分,但是我个人感觉二分是会使时间复杂度升高的
因为不二分的话,只跑一次匈牙利就可以,用二分的话,可能要跑多次
或者是有更好的二分方法我没有想到
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int maxn=50005,maxm=15;
char s[maxm][maxm];
int n,m,tot;
int movex[maxm]={1,-1,0,0};
int movey[maxm]={0,0,-1,1};
int dis[maxm][maxm][maxm][maxm];
vector<int> g[maxn];
int match[maxn],vis[maxn];
vector<int> dx,dy,px,py;
int dfs(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(match[u]==-1 || dfs(match[u])){
match[u]=xx;
return 1;
}
}
}
return 0;
}
void Init(){
memset(dis,0x3f,sizeof(dis));
for(int i=0;i<maxn;i++){
g[i].clear();
}
dx.clear(),dy.clear(),px.clear(),py.clear();
memset(match,-1,sizeof(match));
memset(vis,0,sizeof(vis));
}
void bfs(int xx,int yy){
queue<int> qx,qy;
qx.push(xx),qy.push(yy);
dis[xx][yy][xx][yy]=0;
while(!qx.empty()){
int nx=qx.front();
int ny=qy.front();
qx.pop(),qy.pop();
for(int i=0;i<4;i++){
int mx=movex[i]+nx;
int my=movey[i]+ny;
if(mx>n || mx<=0 || my>m || my<=0 || s[mx][my]!='.') continue;
if(dis[xx][yy][mx][my]>dis[xx][yy][nx][ny]+1){
dis[xx][yy][mx][my]=dis[xx][yy][nx][ny]+1;
qx.push(mx),qy.push(my);
}
}
}
}
void solve(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='D'){
dx.push_back(i),dy.push_back(j);
bfs(i,j);
}
else if(s[i][j]=='.') px.push_back(i),py.push_back(j);
}
}
int d=dx.size(),p=px.size();
for(int i=0;i<d;i++){
for(int j=0;j<p;j++){
int nows=dis[dx[i]][dy[i]][px[j]][py[j]];
if(nows!=0x3f3f3f3f){
for(int k=nows;k<=tot;k++){
g[k*d+i].push_back(j);
}
}
}
}
if(p==0){
printf("0
");
return;
}
int ans=0;
for(int i=0;i<tot*d;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)){
if(++ans==p){
printf("%d
",i/d);
return;
}
}
}
printf("impossible
");
}
int main(){
int t;
scanf("%d",&t);
while(t--){
Init();
scanf("%d%d",&n,&m);
tot=n*m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf(" %c",&s[i][j]);
}
}
solve();
}
return 0;
}
类型二:闯关问题
描述:给你一些关卡和限制条件见,问最多可以通过几关
2.1 超级英雄
题目描述
现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的多少获得不同数目的奖品或奖金。
主持人问题准备了若干道题目,只有当选手正确回答一道题后,才能进入下一题,否则就被淘汰。为了增加节目的趣味性并适当降低难度,主持人总提供给选手几个“锦囊妙计”,比如求助现场观众,或者去掉若干个错误答案(选择题)等等。
这里,我们把规则稍微改变一下。假设主持人总共有m
道题,选手有n
种不同的“锦囊妙计”。主持人规定,每道题都可以从两种“锦囊妙计”中选择一种,而每种“锦囊妙计”只能用一次。我们又假设一道题使用了它允许的锦囊妙计后,就一定能正确回答,顺利进入下一题。
现在我来到了节目现场,可是我实在是太笨了,以至于一道题也不会做,每道题只好借助使用“锦囊妙计”来通过。如果我事先就知道了每道题能够使用哪两种“锦囊妙计”,那么你能告诉我怎样选择才能通过最多的题数吗?
输入格式
输入文件的一行是两个正整数n
和m(0 < n <1001,0 < m < 1001)
表示总共有n
中“锦囊妙计”,编号伟0~n-1
,总共有m
个问题。
以下的m
行,每行两个数,分别表示第m
个问题可以使用的“锦囊妙计”的编号。
注意,每种编号的“锦囊妙计”只能使用一次,同一个问题的两个“锦囊妙计”可能一样。
输出格式
第一行为最多能通过的题数p
样例
样例输入
5 6
3 2
2 0
0 3
0 4
3 2
3 2
样例输出
4
分析
比较裸的板子,将每一道题和这一道题能够使用的锦囊妙计建一条边,然后从小到大枚举跑最大匹配,只要有一道题不能匹配,就跳出循环,这也是闯关问题一个独特的地方。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
vector<int> g[maxn];
int vis[maxn],pp[maxn];
int dfs(int xx){
int tot=g[xx].size();
for(int i=0;i<tot;i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(!pp[u] || dfs(pp[u])){
pp[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int aa,bb;
scanf("%d%d",&aa,&bb);
aa++,bb++;
g[i].push_back(aa);
g[i].push_back(bb);
}
int ans=0;
for(int i=1;i<=m;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
else break;
}
printf("%d
",ans);
return 0;
}
2.2 SCOI 2010 游戏
Description
lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。 游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个属性值为3的装备攻击boss……以此类推。 现在lxhgww想知道他最多能连续攻击boss多少次?
Input
输入的第一行是一个整数N,表示lxhgww拥有N种装备 接下来N行,是对这N种装备的描述,每行2个数字,表示第i种装备的2个属性值
Output
输出一行,包括1个数字,表示lxhgww最多能连续攻击的次数。
Sample Input
3
1 2
3 2
4 5
Sample Output
2
HINT
【数据范围】
对于30%的数据,保证N < =1000
对于100%的数据,保证N < =1000000
分析
对于第(i)件装备,我们把它和它的两个属性建边,最后从小到大枚举一遍伤害
要注意的是,你不能枚举到(n),应该枚举到(n+1)
这样做思想是正确的,但是会T掉
所以尽量还是用并查集来做
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e6+10;
vector<int> g[N];
int vis[N],match[N],rt;
bool dfs(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(vis[u]==0){
vis[u]=1;
if(match[u]==-1||dfs(match[u])){
match[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
memset(match,-1,sizeof(match));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
g[a].push_back(i);
g[b].push_back(i);
}
int ans=0;
for(int i=1;i<=n+1;i++){
memset(vis,0,sizeof(vis));
if(!dfs(i)){
printf("%d
",i-1);
return 0;
}
}
}
类型三:消除问题
描述:在图中有一些障碍物,你可以一次消灭一行,也可以消灭一列,问最少多少次可以消灭完
3.1 Asteroids 穿越小行星群
题目描述
贝茜想驾驶她的飞船穿过危险的小行星群.小行星群是一个的网格(1≤N≤500)
,在网格内有K
个小行星(1≤K≤10000)
.
幸运地是贝茜有一个很强大的武器,一次可以消除所有在一行或一列中的小行星,这种武器很贵,所以她希望尽量地少用.给出所有的小行星的位置,算出贝茜最少需要多少次射击就能消除所有的小行星.
输入格式
第1
行:两个整数N
和K
,用一个空格隔开.
第2
行至K+1
行:每一行有两个空格隔开的整数R, C(1≤R, C≤N)
,分别表示小行星所在的行和列.
输出格式
一个整数表示贝茜需要的最少射击次数,可以消除所有的小行星
样例
样例输入
3 4
1 1
1 3
2 2
3 2
样例输出
2
分析
因为我们一次可以消除一行的小行星,我们就可以把小行星的行与列之间建一条边
这样,我们每消除一个点,就相当于把一行或者一列的小行星消除完
最后跑一个匈牙利就可以了
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=600;
vector<int> g[maxn];
int vis[maxn],gj[maxn],n,k;
int zhao(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(gj[u]==0||zhao(gj[u])){
gj[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++){
int aa,bb;
scanf("%d%d",&aa,&bb);
g[aa].push_back(bb);
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(zhao(i)) ans++;
}
printf("%d
",ans);
return 0;
}
3.2 放置机器人
题目描述
Robert
是一位著名的工程师。一天他的老板给了他一个任务。任务的背景如下:
给出一张由方块组成的地图。方块有许多种:墙,草,和空地。老板想让Robert
在地图上放置尽可能多的机器人。每个机器人拿着一把激光枪,它可以同时向东西南北四个方向射击。机器人必须一直呆在它开始时被放在的位置并且不断地射击。激光束当然可以经过空地或草地,但不能穿过墙。机器人只能被放在空地上。
当然老板不希望看到机器人相互攻击。换句话说,两个机器人不能被放在一条线上(竖直或水平),除非它们中间有一堵墙。
由于你是一位机智的程序员和Robert
的好基友之一,他请你帮他解决这个问题。也就是说,给出地图的描述,计算地图上最多能放置的机器人数量。
输入格式
输入文件的第一行有两个正整数m,n(1<=m,n<=50)
,即地图的行数和列数。
接下来有m
行,每行n
个字符,这些字符是#
,*
或o
,它们分别代表墙,草和空地。
输出格式
输出一行一个正整数,即地图中最多放置的机器人数目
样例
样例输入
sample 1:
4 4
o***
*###
oo#o
***o
sample 2:
4 4
#ooo
o#oo
oo#o
***#
样例输出
sample 1:
3
sample 2:
5
提示
分析
思路同上,不同的是我们要先把横向联通的模块处理出来,再把纵向联通的模块的处理出来,如果两个模块有交点,就把这两个模块连边,最后再用一个匈牙利算法求一下最大匹配
最后大家要注意一下输出,不要把sample(XX)也输出了
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2505;
vector<int> g[maxn];
int match[maxn];
bool vis[maxn];
char s[maxn],s1[maxn];
int dfs(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(match[u]==-1 || dfs(match[u])==1){
match[u]=xx;
return 1;
}
}
}
return 0;
}
char jl[maxn][maxn];
int shuyuh[maxn][maxn],shuyul[maxn][maxn];
int main(){
// while(scanf("%s",s)!=EOF){
/*scanf("%s",s1);
getchar();
getchar();*/
memset(match,-1,sizeof(match));
memset(shuyuh,0,sizeof(shuyuh));
memset(shuyul,0,sizeof(shuyul));
for(int i=0;i<maxn;i++){
g[i].clear();
}
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",jl[i]+1);
}
int Hcnt=0,Lcnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(jl[i][j]!='o') continue;
shuyuh[i][j]=++Hcnt;
while(jl[i][++j]!='#' && j<=m){
if(jl[i][j]=='o') shuyuh[i][j]=Hcnt;
}
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(jl[j][i]!='o') continue;
shuyul[j][i]=++Lcnt;
while(jl[++j][i]!='#' && j<=n){
if(jl[j][i]=='o') shuyul[j][i]=Lcnt;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(jl[i][j]!='o') continue;
g[shuyuh[i][j]].push_back(shuyul[i][j]);
}
}
int ans=0;
for(int i=1;i<=Hcnt;i++){
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
/* printf("%s %s
",s,s1);*/
printf("%d
",ans);
// getchar();
// }
return 0;
}
类型四:最小路径覆盖
描述:给你一个图,求最小路径覆盖
4.1 Treasure Exploration
题目描述
Have you ever read any book about treasure exploration? Have you ever see any film about treasure exploration? Have you ever explored treasure? If you never have such experiences, you would never know what fun treasure exploring brings to you. Recently, a company named EUC (Exploring the Unknown Company) plan to explore an unknown place on Mars, which is considered full of treasure. For fast development of technology and bad environment for human beings, EUC sends some robots to explore the treasure. To make it easy, we use a graph, which is formed by N points (these N points are numbered from 1 to N), to represent the places to be explored. And some points are connected by one-way road, which means that, through the road, a robot can only move from one end to the other end, but cannot move back. For some unknown reasons, there is no circle in this graph. The robots can be sent to any point from Earth by rockets. After landing, the robot can visit some points through the roads, and it can choose some points, which are on its roads, to explore. You should notice that the roads of two different robots may contain some same point. For financial reason, EUC wants to use minimal number of robots to explore all the points on Mars. As an ICPCer, who has excellent programming skill, can your help EUC?
题目概述:
给你一个有向无环图,有n个点,m条有向边。
一个机器人可以从任意点开始沿着边的方向走下去。对于每一个机器人:走过的点不能再走过。
问你最少用几个机器人可以走完所有的n个点,不同的机器人可以走相同的点。
输入格式
The input will consist of several test cases. For each test case, two integers N (1 <= N <= 500) and M (0 <= M <= 5000) are given in the first line, indicating the number of points and the number of one-way roads in the graph respectively. Each of the following M lines contains two different integers A and B, indicating there is a one-way from A to B (0 < A, B <= N). The input is terminated by a single line with two zeros.
输出格式
For each test of the input, print a line containing the least robots needed.
样例
样例输入
1 0
2 1
1 2
2 0
0 0
样例输出
1
1
2
分析
先补充一下二分图的几个扩展定理
- 最大匹配数:最大匹配的匹配边的数目
- 最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择
- 最大独立数:选取最多的点,使任意所选两点均不相连
- 最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 0(即单个点)。
- 定理
- 定理一:最大匹配数 = 最小点覆盖数(这是 Konig 定理)
- 定理2:最大匹配数 = 最大独立数
- 定理3:最小路径覆盖数 = 顶点数 - 最大匹配数
这道题要用到的定理就是定理三
求最大匹配数的话,我们可以用Floyd,也可以用bitset
大致思路就是A到B有一条边,B到C有一条边,那么A也可以到C
用bitset的话不到50ms就可以过,用Floyd则要800ms
代码
Floyd版
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
const int maxn=505;
vector<int> g[maxn];
int match[maxn];
bool vis[maxn];
int gg[maxn][maxn];
int dfs(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(match[u]==-1 || dfs(match[u])==1){
match[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF && n!=0){
memset(match,-1,sizeof(match));
memset(gg,0,sizeof(gg));
for(int i=0;i<maxn;i++){
g[i].clear();
}
for(int i=1;i<=m;i++){
int aa,bb;
scanf("%d%d",&aa,&bb);
gg[aa][bb]=1;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(gg[i][k] && gg[k][j]) gg[i][j]=1;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(gg[i][j]) g[i].push_back(j);
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
printf("%d
",n-ans);
}
return 0;
}
bitset版
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
const int maxn=505;
vector<int> g[maxn];
int match[maxn];
bool vis[maxn];
bitset<maxn> gg[maxn];
int dfs(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(match[u]==-1 || dfs(match[u])==1){
match[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF && n!=0){
memset(match,-1,sizeof(match));
for(int i=0;i<maxn;i++){
gg[i].reset();
g[i].clear();
}
for(int i=1;i<=m;i++){
int aa,bb;
scanf("%d%d",&aa,&bb);
gg[aa][bb]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(gg[i][j]) gg[i]|=gg[j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(gg[i][j]) g[i].push_back(j);
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
printf("%d
",n-ans);
}
return 0;
}
类型五:冲突问题
描述:给出你一些限制条件,求满足条件的最大值
5.1 文理分班
题目描述
jzyz
每年的文理分班的时候,每个班都会有一些同学分到其他班,还会进入一些其他班的同学进入这个班。
小x负责安排座位,为了照顾分班带来的那种伤感情绪,小x
制定了很人性化的座位安排计划,具体计划如下:
比如A
和B
都是本班学生且是好朋友,A
分到了其他班,而C
则是外班进入这个班的,C
和A
并不熟悉,而C
和B
关系很好,那么小x
为了照顾A
和C
的情绪,就会让B
坐在A
的位置,C
坐在B
的位置。
当然,实际情况可能很复杂,比如一个班里的同学之间关系不一定好,外班进来的可能和本班很多人关系都很好。
现在告诉你,和小x
所在班有关系的人一共有n
个人,小x
想知道有没有一个合理的方案来满足自己的座位安排计划。
输入格式
本题为多组数据,第一行一个整数M
,表示有M
组测试数据。
对于每组测试数据,每组的第一行一个整数n
,表示一共有n
个人和这个班有关系。
接下来一行n
个整数,第i
个整数表示第i
个人是否是本班学生(0
表示不是,那么这次一定会分到本班,1
表示是,分到其他班的也算是本班学生)
接下来一行n
个整数,第i
个整数表示第i
个人是否要分到其他班(0
表示留在本班,1
表示分到其他班,如果第i
个人不是本班的,那么他这次一定会被分来,那么第i
个整数就是一个随机整数,没有实际意义)
接下来是一个n
行n
列的一个二维矩阵,第i
行第j
列的数表示第i
个人和第j
个人是否关系很好(1
表示认识,0
表示不认识),对角线上是0
,但是自己肯定认识自己。
输出格式
每组数据,如果存在一个方案,输出 “^_^”(不含引号)。
如果没有方案,输出 “T_T”(不含引号)。都是半角字符。
样例
样例输入
1
3
1 1 0
0 1 0
0 1 1
1 0 0
1 0 0
样例输出
^_^
数据范围与提示
对于 30% 的数据满足 1 ≤ n ≤ 12。 对于 100% 的数据满足 1 ≤ n ≤ 50,1 ≤ T ≤ 20。
分析
大体思路就是把座位看成一堆点,把人看成另一堆点
如果某个人可以坐某个座位,那么把这一个人和这个座位连边
注意自己也可以和自己的座位连边
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
bool benban[maxn],fendaowaiban[maxn];
vector<int> g[maxn];
bool vis[maxn];
int match[maxn];
int zhao(int xx){
for(int i=0;i<g[xx].size();i++){
int u=g[xx][i];
if(!vis[u]){
vis[u]=1;
if(match[u]==-1 ||zhao(match[u])){
match[u]=xx;
return 1;
}
}
}
return 0;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(match,-1,sizeof(match));
memset(benban,0,sizeof(benban));
memset(fendaowaiban,0,sizeof(fendaowaiban));
for(int i=0;i<maxn;i++){
g[i].clear();
}
int tot=0;
int n;
scanf("%d",&n);
tot=n;
for(int i=1;i<=n;i++){
int aa;
scanf("%d",&aa);
if(aa==0) benban[i]=0;
else benban[i]=1;
}
for(int i=1;i<=n;i++){
int aa;
scanf("%d",&aa);
if(benban[i]){
if(aa==0) fendaowaiban[i]=0;
else fendaowaiban[i]=1;
tot-=fendaowaiban[i];
}
}
for(int i=1;i<=n;i++){
if(benban[i] && fendaowaiban[i]==0) g[i].push_back(i);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int aa;
scanf("%d",&aa);
if(i==j ||fendaowaiban[i] || aa==0 || !benban[j]) continue;
g[i].push_back(j);
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(zhao(i) && fendaowaiban[i]==0) ans++;
}
if(tot==ans) printf("^_^
");
else printf("T_T
");
}
return 0;
}
5.2 猫和狗
题目描述
小k同学正在玩一个游戏,在游戏中他扮演了一个马戏团的老板,现在小k同学需要利用马戏团中的A只猫和B只狗举办一次表演,表演之前他让观众进行了投票,投票的类容是:我想看到第___号猫/狗的表演,不想看到第___号猫/狗的表演。注意到每个观众都是更喜欢猫或更喜欢狗,所以两个空后面一定会被勾上不同的内容。喜欢猫的观众会在第一空后面选择猫,第二空后面选择狗;反之就会在第一空后面选择狗,第二空后面选择猫。对于每一个观众,只有当TA投票的内容都被满足了(即TA想看到的动物出场表演,TA不想看到的动物不参与表演)的时候,TA才会来看表演。当然啦,看表演是要付门票钱的,作为马戏团的老板,小k自然是希望来的人越多越好了。他想找到一个安排动物表演的方案,使得来看表演的观众尽量多。
输入格式
第1行3个正整数n,m,k,分别表示猫、狗和观众的数量
第2~k+1行,每行描述了一个观众的投票内容。
首先输入一个字符C或D紧接着是一个数字,表示某个观众想看到的动物,然后是一个空格隔开,接下去又是一个C或D加上一个数字,表示这个观众不想看到的动物,同一行中一定不会出现两个C或两个D。
输出格式
输出一行一个正整数,表示小k在最优的安排下可以吸引多少观众来看表演。
样例
样例输入
1 2 4
C1 D1
C1 D1
C1 D2
D2 C1
样例输出
3
数据范围与提示
对于25%的数据n,m≤10,k≤25; 对于100%的数据,n,m≤300,k≤500.
分析
把喜欢猫的弄到一堆,把喜欢狗的弄到另一堆
分别枚举一遍,如果冲突的话就建边
要用到一个定理:最大匹配数 = 最大独立数
最后再用顶点数减去最大匹配数即可
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,question,catnum,dognum;
int a[601][601],result[20000],tot=0;
int id[601][601];
bool state[1000];
vector<int> g[5000];
bool find(int aa)
{
for(int j=0;j<g[aa].size();j++){
int u=g[aa][j];
if(state[u]==0)
{
state[u]=1;
if(result[u]==0||find(result[u]))
{
result[u]=aa;
return 1;
}
}
}
return 0;
}
int main()
{
cin>>catnum>>dognum>>n;
int x,y;
string aa[600],bb[600];
for(int i=1;i<=n;i++)
{
cin>>aa[i];
cin>>bb[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(aa[i]==bb[j]) g[i].push_back(j),g[j].push_back(i);
}
for(int i=1;i<=n;i++)
{
memset(state,0,sizeof(state));
if(find(i)) tot++;
}
cout<<n-tot/2<<endl;
}