ACM Steps_Chapter Six_Section3

Strategic Game

/*
做该题要掌握以下两点:

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;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值