BZOJ 2150+BZOJ 1854【二分图匹配】 两更+蜜汁数组(游戏+部落战争)

ps.看帖回帖是种美德      

  今天GET了一个新技能——二分图的匹配问题。

       怎么解释呢?就是有A,B两个集合,要求里面元素尽量1对1匹配。

下面的专业解释摘自 (谁叫我懒呢2333)

Renfei's Blog——

二分图的最大匹配、完美匹配

这篇文章讲无权二分图(unweighted bipartite graph)的最大匹配(maximum matching)和完美匹配(perfect matching),以及用于求解匹配的匈牙利算法(Hungarian Algorithm);不讲带权二分图的最佳匹配。

二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交集  U U 和 V V ,使得每一条边都分别连接 U U V V中的顶点。如果存在这样的划分,则此图为一个二分图。二分图的一个等价定义是:不含有「含奇数条边的环」的图。图 1 是一个二分图。为了清晰,我们以后都把它画成图 2 的形式。

匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。例如,图 3、图 4 中红色的边就是图 2 的匹配。

Bipartite Graph(1)  Bipartite Graph(2)  Matching  Maximum Matching

我们定义匹配点匹配边未匹配点非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边。

完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。图 4 是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。

举例来说:如下图所示,如果在某一对男孩和女孩之间存在相连的边,就意味着他们彼此喜欢。是否可能让所有男孩和女孩两两配对,使得每对儿都互相喜欢呢?图论中,这就是完美匹配问题。如果换一个说法:最多有多少互相喜欢的男孩/女孩可以配对儿?这就是最大匹配问题。

0


好了,二分图匹配是啥基本上就解释完了,下面开始氪题(下面就是我自己写的了)。


第一题    【游戏】

Time Limit 1s Memory Limit 128M

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

这道题其实就是裸的匈牙利算法(<--这是什么我就不解释了),因为每一把武器都有两种属性,但是我们最多只需要它的其中一种(有的武器可能我们根本用不上)。所以我们就把武器和攻击力进行一一匹配。

再看数据范围,很明显如果我们老老实实完整跑一遍匈牙利,是会超时的。反正他求的是连击数,所以当我们无法匹配时,连击就断了,就立即跳出循环。

上代码。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define in(x) scanf("%d",&x)
#define maxn 2000000+10//蜜汁数组 
using namespace std;
int n;
int a[maxn],b[maxn];
int ans;
int vis[maxn];
int f[maxn];
struct Mad{
	int v,next;
}e[maxn*2];
int k=1;
int head[maxn*2];

void adde(int a,int b)
{
	e[k].next=head[a];		
	e[k].v=b;
	head[a]=k++;
}

int find(int x)//进来的是武器编号 
{
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		if(!vis[i])
		{
			vis[i]=1;
			if(!f[i]||find(f[i]))//有没有匹配过当前的攻击力,如果匹配了,看看能不能把之前的武器换个攻击力匹配 
			{
					
				f[i]=x;
				return 1;
			}
		}
	}
	return 0;
}

int main()
{
	//freopen("A.in","r",stdin);
	//freopen("A.out","w",stdout);
	memset(head,-1,sizeof(head));
	
	int x,y;
	in(n);
	for(int i=1;i<=n;i++)
	{
		in(x);
		in(y);
		adde(x,i);//预处理
		adde(y,i);//建二分图
	}
	
	int i;
	for(i=1;i<=n;i++)
	{
		if(!find(i)) break;//跳出循环 
	}
	

	
	printf("%d\n",i-1);
	return 0;
}

大家也看到了,我的数组开得大破天际,如果我按照题目要求开1000000+的话,最后几组数据的答案会蜜汁变大,如果有谁发现为什么,希望能告知一二,谢谢。

第二题 【部落战争】

Time Limit 1s Memory Limit 128M

Description

lanzerb的部落在A国的上部,他们不满天寒地冻的环境,于是准备向A国的下部征战来获得更大的领土。A国是一个M*N的矩阵,其中某些地方是城镇,某些地方是高山深涧无人居住。lanzerb把自己的部落分成若干支军队,他们约定:1.每支军队可以从任意一个城镇出发,并只能从上往向下征战,不能回头。途中只能经过城镇,不能经过高山深涧。2.如果某个城镇被某支军队到过,则其他军队不能再去那个城镇了。3.每支军队都可以在任意一个城镇停止征战。4.所有军队都很奇怪,他们走的方法有点像国际象棋中的马。不过马每次只能走1*2的路线,而他们只能走R*C的路线。lanzerb的野心使得他的目标是统一全国,但是兵力的限制使得他们在配备人手时力不从心。假设他们每支军队都能顺利占领这支军队经过的所有城镇,请你帮lanzerb算算至少要多少支军队才能完成统一全国的大业。

Input

第一行包含4个整数MNRC,意义见问题描述。接下来M行每行一个长度为N的字符串。如果某个字符是'.',表示这个地方是城镇;如果这个字符时'x',表示这个地方是高山深涧。

Output

输出一个整数,表示最少的军队个数。

Sample Input

3 3 1 2

...

.x.

...

 

Sample Output

4

Sample Input

5 4 1 1

....

..x.

...x

....

x...

Sample Output

5

 

Hint

30%数据有n,m<=10 r,c<=5  

60%数据有n,m<=25 r,c<=10  

100%数据保证n,m<=50 r,c<=10

这道题就不是裸的匈牙利问题了,不过需要这种算法(这TM不是废话吗)。我们一看题目和样例数据,会发现这道题其实不是标准二分图,那么我们首先要做的事就是建图,把能到的点都和当前点连接起来加一条边。
	in(M);in(n);in(R);in(C);
	for(int i=0;i<M;i++)
	for(int j=0;j<n;j++)
	{
		cin>>s;
		if(s=='.') 
		{
			v++;//记录点的个数
			m[i][j]=v;//标记点的顺序方便待会建边
		}
		
	}
	
	for(int i=0;i<M;i++)
	for(int j=0;j<n;j++)
	{//有四种走法,大家不妨推推
		int x=m[i][j];
		
		if(i+R<M&&j+C<n)
		if(m[i+R][j+C]) adde(x,m[i+R][j+C]);
		
		if(i+C<M&&j+R<n)
		if(m[i+C][j+R]) adde(x,m[i+C][j+R]);
		
		if(i+R<M&&j-C>=0)
		if(m[i+R][j-C]) adde(x,m[i+R][j-C]);
		
		if(i+C<M&&j-R>=0)
		if(m[i+C][j-R]) adde(x,m[i+C][j-R]);
	}


然后我们会发现,这是那什么最短路径覆盖,他的解就是——点的个数-最大匹配数,对!Mad我们又要求最大匹配数,又是匈牙利算法!
<span style="color:#33cc00;">#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define in(x) scanf("%d",&x)
#define maxn 100+10
using namespace std;
int M,n,R,C;
int v,ans;
int m[250*2][250*2];
int vis[5000*2];
int used[5000*2];
struct Mad{
	int v,next;
}e[5000*2];
int head[5000*2];
int k=1;

void adde(int a,int b)
{
	e[k].v=b;
	e[k].next=head[a];
	head[a]=k++;
}

int find(int x)
{
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int v=e[i].v;
		if(!vis[v])
		{
			vis[v]=1;
			if(!used[v]||find(used[v]))
			{
				used[v]=x;
				return 1;
			}
		}
	}
	return 0;
}

int main()
{
	//freopen("C.in","r",stdin);
	//freopen("C.out","w",stdout);
	char s;
	memset(head,-1,sizeof(head));
	in(M);in(n);in(R);in(C);
	for(int i=0;i<M;i++)
	for(int j=0;j<n;j++)
	{
		cin>>s;
		if(s=='.') 
		{
			v++;
			m[i][j]=v;
		}
		
	}
	
	for(int i=0;i<M;i++)
	for(int j=0;j<n;j++)
	{
		int x=m[i][j];
		
		if(i+R<M&&j+C<n)
		if(m[i+R][j+C]) adde(x,m[i+R][j+C]);
		
		if(i+C<M&&j+R<n)
		if(m[i+C][j+R]) adde(x,m[i+C][j+R]);
		
		if(i+R<M&&j-C>=0)
		if(m[i+R][j-C]) adde(x,m[i+R][j-C]);
		
		if(i+C<M&&j-R>=0)
		if(m[i+C][j-R]) adde(x,m[i+C][j-R]);
	}
	
	for(int i=1;i<=v;i++)
	{
		memset(vis,0,sizeof(vis));
		ans+=find(i);
	}
	
	ans=v-ans;
	printf("%d\n",ans);
	return 0;
}</span>


好的,然后我们又发现我的数组大得离谱,因为我按照题目要求开数组Mad只过6组啊有木有!!!!!!!!!!!



ps.看帖回帖是种美德


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值