1854: [Scoi2010]游戏


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

极简题解:

一条边上的两个数字,优先让小数字打上标志。

如果小数字已打上了,再让大数字来打。

于是对于(a,b)来说,如果a<b,则较大的数字b,暂时不能打标记

但再个(b,c),且b<c,则b就不是目前最大的数字了,C才是最大的数字,于是b也可以打上标记了。

这正好符合并查集的传递性。

Sol:

这个题还是挺有意思的,思想用贪心和并查集。

对于连接u,v的边。

因为要保证攻击从1开始,于是我们每次用并查集去并的时候,强行让大的数字成为小的数字的父亲,并给小数字打上标志。

这样一直连下去,于是整个集合的根结点是唯一没有被打上标记的点。

于是当连接某个边,使得有环出现了,则这个时候,可以给集合的根结点打上标记了(因为整个集合只有它没打上标志了)

此时如果再来连边,就发现边的一个端点u打了标记,但另一端点v还没打上标记,于是给v也打上标记好了。

例如有以下物品时
1 2
2 3
3 4
2 5
前三个构成如下图,只有4没有被选中

对于第4个(2,5),2的父亲是4,5的父亲是5。
于是用4连5,同时标志上4,5做为父亲点,图如下:

如果此时再来个物品(1,5),则发现1与5在一个集合中了。(如果是来个(2,5)也是一样的)

5是这个集合中唯一没有被标记上的,则此时就可以直接标记5了。

 如果此时再来个(5,6).则发现既然5已标记上了,于是6也就可以标记上了。

想着:并查集本质上是一种树结构

将每个装备看做边,装备属性看做点,每次合并两个点

将过程想象成给点搭配边的过程

这样得到的连通块有两类

1、没有环,即边数=点数-1,那么对于树内的任意n-1的点,都可以搭配一条边,

但题目要求从小到大攻击,所以先给小的搭配。也就是说,树内只有最大的点没有搭配边。

如何实现这一过程?在合并时,让小的合并到大的上面,也就是始终让树内最大的点是祖先节点

2、边数>=n,有环或有重边。

#include<bits/stdc++.h>
using namespace std;
int ans,q,n,m,f[1008001];
bool flag[1001011];
int find(int x) {
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
}
void coco(int x,int y) 
{
	if(flag[x]==true) 
	    flag[y]=true;
	else 
	    flag[x]=true;
	f[x]=y;
}
void unionset(int x,int y) 
{
	int xx=find(x);
	int yy=find(y);
	if(xx>yy) 
swap(xx,yy); if(xx!=yy)
//如果不在一个集合中,此时优先给编号小的(设为xx)打上标记,如果xx打过了,就给yy打标记 coco(xx,yy); else //如果在一个集合中,则此时集合中只有根结点没有打上标志了,于是给它打上 flag[xx]=true; } int main() { scanf("%d",&n); for(int i=1; i<=100001; i++) f[i]=i; for(int i=1,x,y; i<=n; i++) { scanf("%d%d",&x,&y); unionset(x,y); } for(int i=1; i<=100001; i++) if(flag[i]==false) { ans=i; break; } printf("%d",ans-1); return 0; }

//wrong,这个程序对于一个环,再连一条边出来的情况,没有考虑到。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,fa[10001];
bool v[10001];
int find(int i)
{
return fa[i]==i ? i:fa[i]=find(fa[i]);
}
int main()
{
for(int i=1;i<=10000;i++) fa[i]=i;
scanf("%d",&n);
int a,b,r1,r2;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a,&b);
r1=find(a);r2=find(b);
if(r1==r2) v[r1]=true;
else
{
if(r1>r2) swap(r1,r2);
fa[r1]=r2;
v[r1]=true;
}
}
int i;
for(i=1;v[i];i++);
printf("%d",i-1);
}

原文地址:https://www.cnblogs.com/cutemush/p/14674013.html