bzoj1854 游戏题解(二分图/并查集)
1854: [Scoi2010]游戏
Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 5547 Solved: 2229
[Submit][Status][Discuss]
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
1 2
3 2
4 5
Sample Output
2
HINT
【数据范围】
对于30%的数据,保证N < =1000
对于100%的数据,保证N < =1000000
Source
这道题说实在的打法有很多种,我个人首先是想到了二分图,但二分图的时间复杂度理论上最大为O(10000*N),在BZOJ上没有过,但在校内网上过了,基本还是那个思路,将全职与武器连边,然后跑一个普通的二分图就好了。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<cstring> 5 #include<queue> 6 #include<algorithm> 7 #include<cmath> 8 #include<map> 9 #include<vector> 10 #define N 1000005 11 using namespace std; 12 int n,zz,a[N]; 13 struct ro 14 { 15 int to,next; 16 }road[N*3]; 17 void build(int x,int y) 18 { 19 zz++; 20 road[zz].to=y; 21 road[zz].next=a[x]; 22 a[x]=zz; 23 } 24 int b[N]; 25 bool fw[N],fw2[N]; 26 bool find(int x) 27 { 28 for(int i=a[x];i>0;i=road[i].next) 29 { 30 int y=road[i].to; 31 if(!fw[y]) 32 { 33 fw[y]=1; 34 if(!b[y]) 35 { 36 b[y]=x; 37 return 1; 38 } 39 } 40 } 41 for(int i=a[x];i>0;i=road[i].next) 42 { 43 int y=road[i].to; 44 if(!fw2[y]) 45 { 46 fw2[y]=1; 47 if(find(b[y])) 48 { 49 b[y]=x; 50 return 1; 51 } 52 } 53 } 54 return 0; 55 } 56 int js[N]; 57 int main() 58 { 59 scanf("%d",&n); 60 for(int i=1;i<=n;i++) 61 { 62 int x,y; 63 scanf("%d%d",&x,&y); 64 js[x]++,js[y]++; 65 build(x,i); 66 build(y,i); 67 } 68 int ans=0; 69 for(int i=1;i<=min(n,10000);i++) 70 { 71 if(!js[i])break; 72 memset(fw,0,sizeof(fw)); 73 memset(fw2,0,sizeof(fw2)); 74 if(find(i))ans++; 75 else break; 76 } 77 printf("%d\n",ans); 78 return 0; 79 }
下面我来说一下正解:并查集。
当时在思考时并不是没想到过并查集,但如何去保证递增这个条件没想出来,然后就放弃这个思路了。合并那些并查集十分好说,毕竟怎么搞也只能从武器的两个权值做文章那如何保证递增呢?
首先我们明确一个性质当当前连通块是一个树时,如果我们再去加一个边这个图中一定有一个环,而如果所有边都是双向边,我们可以满足每一个点都可以和一个与之相连的边配对且所有边不重复。
那么当我们在并查集合并的时候,我们可以分类讨论,如果说两个节点所属同一个并查集,那么这个并查集里所有的点都可以被满足,如果两个点属于不同并查集,那么我们根据递增的要求,首先看编号小的并查集是否已经被满足,若被满足我们就去满足大的,否则就去满足小的就好了。在合并时我们应当把大的作为小的的fa,否则我们每次满足的标记都只能打给最小的,无法真正更新答案。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<cstring> 5 #include<queue> 6 #include<algorithm> 7 #include<cmath> 8 #include<map> 9 #include<vector> 10 #define N 1000005 11 using namespace std; 12 int n,fa[N]; 13 bool vi[N]; 14 int find(int x) 15 { 16 if(fa[x]==x) return x; 17 return fa[x]=find(fa[x]); 18 } 19 void hb(int x,int y) 20 { 21 int a=find(x),b=find(y); 22 if(a>b)swap(a,b); 23 if(a==b) vi[a]=1; 24 else 25 { 26 if(!vi[a]) vi[a]=1; 27 else vi[b]=1; 28 fa[a]=b; 29 } 30 } 31 int main() 32 { 33 scanf("%d",&n); 34 for(int i=1;i<=10000;i++) 35 fa[i]=i; 36 for(int i=1;i<=n;i++) 37 { 38 int x,y; 39 scanf("%d%d",&x,&y); 40 hb(x,y); 41 } 42 for(int i=1;i<=10001;i++) 43 { 44 if(!vi[i]) 45 { 46 printf("%d\n",i-1); 47 break; 48 } 49 } 50 return 0; 51 }