[SCOI2010] 连续攻击游戏

题目

原题链接

解说

抱歉有点事要忙只能引用了……
这道题正解是用二分图,但是做完这道题的第二题才讲……所以我采用的我当时会的做法:并查集。

我们每有一个武器(a, b)时我们可以把它当做一条边(a, b)。
然后对于构图之后,一个大小为k联通块,我们发现有如下性质:
——如果这个联通块没有环(树):因为有k-1条边,那么总有方法使其中的k-1个节点被覆盖。容易知道这个联通块中一定不选最大的点。
——如果这个联通块有环:那么这k个节点一定都可以被覆盖
这时我们可以用并查集去维护联通块有没有环,联通块的大小。
然后我们扫一遍就行了……看看该联通块有没有环,如果没有环的话是不是看它是不是这个联通块的最大一个点。
引自https://www.luogu.com.cn/blog/user1923/solution-p1640

代码

(注释我写的)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+3;//注意开n的范围而不是开数的范围
//因为攻击n次所以数最大只能用到n,后面比n大的都不用管
int fa[maxn],size[maxn];
bool cir[maxn];
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n+1;i++) fa[i]=i;
	for(int i=1;i<=n+1;i++) size[i]=1;//至今不太明白为啥这两行只初始化到n会WA,洛谷不给数据……
	for(int i=1;i<=n+1;i++){
		int a,b;
		cin>>a>>b;
		int faa=find(a),fab=find(b);
		if(faa==fab) cir[faa]=1;//已经在一个集合里说明有环
		else{//合并
			cir[faa]=cir[faa]|cir[fab];
			cir[fab]=0;
			size[faa]+=size[fab];
			size[fab]=0;
			fa[fab]=faa;
		}
	}
	int ans;
	for(int i=1;i<=n+1;i++){
		int fai=find(i);
		if (!cir[fai]){
            if (size[fai]==1){
				ans=i-1;
				break;
			}
            else size[fai]--;
        }
	}
	cout<<ans;
	return 0;
} 

幸甚至哉,歌以咏志。

原文地址:https://www.cnblogs.com/DarthVictor/p/12827457.html