/*
做该题要掌握以下两点:
1、二分图最小顶点覆盖 = 双向二分图最大匹配 / 2 。
2、利用STL中的vector的可以很方便的建立图的邻接表存储。主要用到push_back()、clear()操作。
3、scanf()和printf()函数可以很方便的实现题中的输入和输出。详见代码。
*/
#include<iostream>
#include<vector>
using namespace std;
const int MAX=1500;
vector<int> map[MAX];
//bool map[MAX][MAX];
bool used[MAX];
int link[MAX];
int n;
void preClear()
{
for(int i=0;i<n;i++)
map[i].clear();
}
void storeMap() //存储二分图
{
preClear();
int i,j,row,col,num;
memset(map,false,sizeof(map));
for(j=0;j<n;j++)
{
scanf("%d:(%d)",&row,&num);
for(i=0;i<num;i++)
{
scanf("%d",&col);
//map[row][col]=true;
//map[col][row]=true;
//邻接表,双向图
map[row].push_back(col);
map[col].push_back(row);
}
}
}
bool can(int t) //确定是否存在增广路径
{
int tmp;
for(int i=0;i<map[t].size();i++)
{
tmp=map[t][i];
if(!used[tmp])
{
used[tmp]=true;
if(link[tmp]==-1 || can(link[tmp]))
{
link[tmp]=t;
return true;
}
}
}
return false;
}
int maxMatch()
{
int num=0;
memset(link,0xff,sizeof(link));
for(int i=0;i<n;i++) //从每一左顶点出发寻找增广路径
{
memset(used,false,sizeof(used));
if(can(i)) num++;
}
return num;
}
int main()
{
while(scanf("%d",&n)==1)
{
storeMap();
printf("%d\n",maxMatch()/2);
}
return 0;
}
Girls and Boys
/**********************************************\
* 最大独立集指的是两两之间没有边的顶点的集合,*
* 顶点最多的独立集成为最大独立集。 *
* 二分图的最大独立集=节点数-(减号)最大匹配数*
\**********************************************/
/*
最大独立集指的是两两之间没有边的顶点的集合,顶点最多的独立集成
为最大独立集。二分图的最大独立集=节点数-(减号)最大匹配数。
For the study reasons it is necessary to find out the maximum set satisfying
the condition: there are no two students in the set who have been "romantically involved"。
由于本题是要找出最大的没有关系的集合,即最大独立集。而求最大独立集重点在于求最大匹配数,
本题中给出的是同学之间的亲密关系,并没有指出哪些是男哪些是女,所以求出的最大匹配数
要除以2才是真正的匹配数
*/
#include<stdio.h>
#include<string.h>
#define MAX1 1005
#define MAX2 1005 //二部图一侧顶点的最大个数
//n,m,MAX1,MAX2需根据实际情况进行修改
//模板开始
int n,m,match[MAX2]; //二分图的两个集合分别含有n和m个元素。
bool visit[MAX2],G[MAX1][MAX2]; //G存储邻接矩阵。
bool DFS(int k)
{ int t;
for(int i = 0; i < m; i++)
{ if(G[k][i] && !visit[i])
{ visit[i] = true; t = match[i]; match[i] = k; //路径取反操作。
if(t == -1 || DFS(t)) return true; //整个算法的核心部分
match[i] = t;
}
}
return false;
}
int Max_match ()
{ int ans = 0,i;
memset(match, -1, sizeof(match));
for(i = 0; i <n ;i++)
{ memset(visit,0,sizeof(visit));
if(DFS(i)) ans++;
}
return ans;
}
// 模板结束
//模板返回的是最大的匹配
int main()
{
int t;
int i,a,b,c;
while(scanf("%d",&t)!=-1)
{
n=t;m=t;
memset(G,0,sizeof(G));
while(t--)
{
scanf("%d: (%d)",&a,&b);
for(i=0;i<b;i++)
{
scanf("%d",&c);
G[a][c]=1;
}
}
printf("%d\n",n-Max_match()/2);
}
return 0;
}
Air Raid
/*
HDU 1151*/
/*
是最小路径覆盖。
注意是有向边。扩展的时候建一边的边。
而且结果不要除于2
二分图就是建模复杂。。。。
写代码都是模板。。。。。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
/* **************************************************************************
//二分图匹配(匈牙利算法的DFS实现)
//初始化:g[][]两边顶点的划分情况
//建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
//g没有边相连则初始化为0
//uN是匹配左边的顶点数,vN是匹配右边的顶点数
//调用:res=hungary();输出最大匹配数
//优点:适用于稠密图,DFS找增广路,实现简洁易于理解
//时间复杂度:O(VE)
//***************************************************************************/
//顶点编号从0开始的
const int MAXN=150;
int uN,vN;//u,v数目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)//从左边开始找增广路径
{
int v;
for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改
if(g[u][v]&&!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{//找增广路,反向
linker[v]=u;
return true;
}
}
return false;//这个不要忘了,经常忘记这句
}
int hungary()
{
int res=0;
int u;
memset(linker,-1,sizeof(linker));
for(u=0;u<uN;u++)
{
memset(used,0,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
//******************************************************************************/
int main()
{
int k;
int n;
int u,v;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
memset(g,0,sizeof(g));
while(k--)
{
scanf("%d%d",&u,&v);
u--;
v--;
g[u][v]=1;
}
uN=vN=n;
printf("%d\n",n-hungary());
}
return 0;
}
50 years, 50 colors
/*
题意:给你一个 n*n 的矩阵,每个格子上对应着相应颜色的气球,
每次你可以选择一行或一列的同种颜色的气球进行踩破,问你在K次这样的操作后,
哪些颜色的气球是不可能被踩破完的
这题确实是点覆盖问题,但是一句话没有看明白 What's more, each time
you can only choose a single row or column of balloon, and crash the balloons
that with the color you had chosen. 导致建图错误(我错误的当成是要连续的才行),
这里其实说的是你可以选一行并把该种颜色的气球都给踩破,并没有要求一定是连续的,
还有一点就是要用set存颜色,这样才能够按照顺序输出,那么建二分图的时候行作为x,
列作为y,如果行列有交点则连上一条边
*/
#include<iostream>
#include<set>
using namespace std;
int g[105][105],mat[105][105],ans[55],mark[55],match[105],used[105],answer[105];
int n,k;
int dfs(int t)
{ for(int i=1;i<=n;i++)
{ if(!used[i]&&mat[t][i])
{ used[i]=1;
if(match[i]==-1||dfs(match[i]))
{ match[i]=t;
return 1;
}
}
}
return 0;
}
int bipartite()
{ int stu,res=0;
memset(match,-1,sizeof(match));
for(stu=1;stu<=n;stu++)
{ memset(used,0,sizeof(used));
if(dfs(stu)) res++;
}
return res;
}
int main()
{ while(scanf("%d%d",&n,&k))
{ if(n==0&&k==0) break;
int i,j,c,num=0;
memset(mark,0,sizeof(mark));
memset(answer,0,sizeof(answer));
memset(ans,0,sizeof(ans));
set<int> v;
set<int> :: iterator it;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{ scanf("%d",&g[i][j]);
v.insert(g[i][j]);
}
int cc=0;
for(it=v.begin();it!=v.end();it++)
{ for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{ if(g[i][j]==*it) mat[i][j]=1;
else mat[i][j]=0; //这里初始化要做到位,否则下一个颜色的时候会产生偏差
}
if((bipartite())>k)
{ answer[cc++]=*it;
}
}
if(cc==0) printf("-1\n");
else
{ printf("%d",answer[0]);
for(i=1;i<cc;i++) printf(" %d",answer[i]);
printf("\n");
}
}
return 0;
}
Card Game Cheater
#include<iostream>
#include<map>
using namespace std;
#define N 60
#define MM N*N
struct node{
int next,v;
node(){};
node(int a,int b){
next=a;v=b;
}
}E[MM];
char card1[N][5];
char card2[N][5];
map<char,int> M;
int head[N],NE,pre[N];
bool h[N];
void init(){
NE=0;
for(int i=0;i<=9;i++)
M[i+'0']=i;
M['T']=10;M['J']=11;M['Q']=12;
M['K']=13;M['A']=14;
M['C']=15;M['D']=16;
M['S']=17;M['H']=18;
memset(head,-1,sizeof(head));
memset(pre,-1,sizeof(pre));
}
void insert(int u,int v){
E[NE]=node(head[u],v);
head[u]=NE++;
}
bool dfs(int u){
for(int i=head[u];i!=-1;i=E[i].next){
int v=E[i].v;
if(!h[v]){
h[v]=1;
if(pre[v]==-1||dfs(pre[v])){
pre[v]=u;
return true;
}
}
}
return false;
}
bool bigger(char *p,char *q){
if(M[p[0]]==M[q[0]])
return M[p[1]]>M[q[1]];
return M[p[0]]>M[q[0]];
}
int main(void){
int t;
scanf("%d",&t);
while(t--){
int n;
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%s",card1[i]);
for(int i=1;i<=n;i++){
scanf("%s",card2[i]);
for(int j=1;j<=n;j++)
if(bigger(card2[i],card1[j]))
insert(i,j+n);
}
int cn=0;
for(int i=1;i<=n;i++){
memset(h,0,sizeof(h));
if(dfs(i))
cn++;
}
printf("%d\n",cn);
}
}
Uncle Tom's Inherited Land*
/*
题意:
给定一个n*m的矩阵,去除里面的黑色小方格,两个相邻的白色小方格为一组,
问一共能找多少组。
很明显的最大匹配,遍历矩阵的每一个可行点,把它和上下左右的可行点连起来,
然后就得到了一个二分图,但这题纠结的地方是如果直接这样建图,每对节点之间就有两条边了,
比如(1,2)--(1,3)和(1,3)--(1,2),这两点之间就有两条边,因为这地方卡了我一上午······
其实仔细想一下就能发现只要跳过相邻的节点建图就可以了,
而相邻节点之间的下标和存在奇偶关系,于是根据下标和的奇偶性建图。
*/
#include<stdio.h>
#include<string.h>
int map[105][105],hash[105][105],vis[105],pre[105],a[105][105];
struct node
{
int x,y;
} id[55];
int cnt,n,m;
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int hungry(int u)
{
int v;
for(v=1;v<cnt;v++)
{
if((id[v].x+id[v].y)%2==1) //去掉下标和为奇数的点
continue;
if(map[u][v]&&vis[v]==0)
{
vis[v]=1;
if(pre[v]==-1||hungry(pre[v]))
{
pre[v]=u;
return 1;
}
}
}
return 0;
}
int main()
{
int i,j,k,t,x,y,ans;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)
break;
scanf("%d",&k);
memset(hash,0,sizeof(hash));
for(i=0;i<k;i++)
{
scanf("%d%d",&x,&y);
hash[x][y]=1;
}
cnt=1;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
if(hash[i][j]==0)
{
a[i][j]=cnt;
id[cnt].x=i;
id[cnt].y=j;
cnt++;
}
}
memset(map,0,sizeof(map));
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if(hash[i][j]==0)
{
for(t=0;t<4;t++)
{
x=i+dx[t];
y=j+dy[t];
if(x>=1&&x<=n&&y>=1&&y<=m&&hash[x][y]==0)
{
map[a[i][j]][a[x][y]]=1;
map[a[x][y]][a[i][j]]=1;
}
}
}
}
}
ans=0;
memset(pre,-1,sizeof(pre));
for(i=1;i<cnt;i++)
{
if((id[i].x+id[i].y)%2==0) //需要注意一下,因为前面匈牙利算法枚举每个v节点,而这里是枚举每个u节点,所以两个的奇偶性不同
continue;
memset(vis,0,sizeof(vis));
if(hungry(i))
ans++;
}
printf("%d\n",ans);
for(i=1;i<cnt;i++)
{
if(pre[i]>=0)
printf("(%d,%d)--(%d,%d)\n",id[i].x,id[i].y,id[pre[i]].x,id[pre[i]].y);
}
printf("\n");
}
return 0;
}
Cat vs. Dog
#include"stdio.h"
#include"string.h"
int chi;
struct node
{
int love;
int heat;
int tot;
int mem[555];
}E[555];
int match[555];
int visit[555];
struct A
{
int t_l;
int l_mem[555];
int t_h;
int h_mem[555];
}ani[222];
int DFS(int k)
{
int i;
for(i=0;i<E[k].tot;i++)
{
if(visit[E[k].mem[i]]) continue;
visit[E[k].mem[i]]=1;
if(match[E[k].mem[i]]==-1 || DFS(match[E[k].mem[i]]))
{
match[E[k].mem[i]]=k;
return 1;
}
}
return 0;
}
int main()
{
int n,m;
int i,l,j;
char str1[10],str2[10];
int f1,f2;
int ans;
while(scanf("%d%d%d",&n,&m,&chi)!=-1)
{
for(i=1;i<=n+m;i++) ani[i].t_h=ani[i].t_l=0;
for(i=1;i<=chi;i++) E[i].tot=0;
for(i=1;i<=chi;i++)
{
scanf("%s%s",str1,str2);
f1=0;
for(l=1;str1[l];l++) {f1*=10;f1+=str1[l]-'0';}
f2=0;
for(l=1;str2[l];l++) {f2*=10;f2+=str2[l]-'0';}
if(str1[0]=='D') f1+=n;
else f2+=n;
E[i].love=f1;
E[i].heat=f2;
ani[f1].l_mem[ani[f1].t_l++]=i;
ani[f2].h_mem[ani[f2].t_h++]=i;
}
for(i=1;i<=chi;i++)
{
E[i].tot=ani[E[i].heat].t_l;
for(l=0;l<E[i].tot;l++) E[i].mem[l]=ani[E[i].heat].l_mem[l];
E[i].tot+=ani[E[i].love].t_h;
for(j=0;l<E[i].tot;l++,j++) E[i].mem[l]=ani[E[i].love].h_mem[j];
}
memset(match,-1,sizeof(match));
ans=0;
for(i=1;i<=chi;i++)
{
memset(visit,0,sizeof(visit));
ans+=DFS(i);
}
printf("%d\n",chi-ans/2);
}
return 0;
}
National Treasures
/*
/很不错的一道题,呵呵,题意是:仓库是r*c的矩阵,每个方格里要么是宝物要么是看守人,
需要求出当前宝物至少需要多少看守人来看守。其中每个宝物需要所有边界点有看守人才能确保安全,
边界点是当前宝物的数值的二进制数从最右边到最左边12位数,如果i位是1则第i个位置是边界点。
因为宝物只能放在原位置或者被看守人替换,所以宝物有两种关系,1.
换成看守人从而解除自己需要的看管的关系。2.有边界点换成看守人来看管自己。
根据图中坐标可以看出,只有横纵坐标之和奇偶性相反的点才有可能建立这样的关系。
那么可以把宝物和看守人都看做同样性质的点,则用最少的点来实现所有关系,就形成了,
最小点覆盖问题。
*/
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
#define M 1500
vector<int> g[M];
int map[55][55],x[55][55],r,c,match[M],v[M],n,m;
int d[12][2]={-1,-2,-2,-1,-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,0,0,1,1,0,0,-1};
int dfs(int k)
{
int i,t;
for(i=0;i<g[k].size();i++)
{
t=g[k][i];
if(!v[t])
{
v[t]=1;
if(match[t]==-1||dfs(match[t]))
{
match[t]=k;
return 1;
}
}
}
return 0;
}
void LY(int a,int b,int cc,int flag)
{
int i,xx,yy;
for(i=0;i<12;i++)
{
if(cc>>i&1)
{
xx=a+d[i][0];
yy=b+d[i][1];
if(xx>=0&&yy>=0&&xx<r&&yy<c&&map[xx][yy]!=-1)
{
if(flag)
g[x[a][b]].push_back(x[xx][yy]);
else
g[x[xx][yy]].push_back(x[a][b]);
}
}
}
}
int main()
{
int i,j,k=0,ans;
while(scanf("%d%d",&r,&c)!=-1,r||c)
{
for(i=0;i<M;i++)g[i].clear();
n=0;m=0;
for(i=0;i<r;i++)
for(j=0;j<c;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j]!=-1)
{
if((i+j)%2)
x[i][j]=n++;
else x[i][j]=m++;
}
}
for(i=0;i<r;i++)
for(j=0;j<c;j++)
if(map[i][j]!=-1)
LY(i,j,map[i][j],(i+j)%2);
ans=0;
memset(match,-1,sizeof(match));
for(i=0;i<n;i++)
{
memset(v,0,sizeof(v));
ans+=dfs(i);
}
printf("%d. %d\n",++k,ans);
}
return 0;
}