题意:
一个游戏有 (n) 个回合,每个回合给出两个数 (a_i) 和 (b_i),每个回合可以选择一个之前没有选择过的数或者不选。现在知道了每个回合给的数是多少,求可以选择的最多的数的数量。
(1leq N leq 10^5,1leq a_i leq 10^9,1 leq b_i leq 10^9)
题目链接:https://ac.nowcoder.com/acm/contest/5673/I
分析:
解法1:【并查集】
首先,对每个回合给出的数进行离散化。对于在同一个回合给出的数,将两个数并在一起。对于一个联通块而言,如果其中没有环的话,一定存在一种选取方式,使得最后联通块中只剩下一个点没有被选择。因此,可以选择的数的数量就是联通块点数 (-1)。对于存在环的联通块而言,一定存在一种方式,使得可以将所有的点全部取完。所以,用并查集求出联通块的数量并标记是否有环即可。
代码1:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int num[N<<1],len,a[N],b[N],fa[N<<1];
bool vis[N<<1];
int getid(int x)
{
return lower_bound(num+1,num+1+len,x)-num;
}
int Find(int x)
{
if(x!=fa[x])
return fa[x]=Find(fa[x]);
else return x;
}
int main()
{
int T,cas=0,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
num[++cnt]=a[i];
num[++cnt]=b[i];
}
sort(num+1,num+1+cnt);
len=unique(num+1,num+1+cnt)-num-1;
for(int i=1;i<=len;i++)
{
fa[i]=i;
vis[i]=false;
}
for(int i=1;i<=n;i++)
{
int x=getid(a[i]);
int y=getid(b[i]);
int fx=Find(x),fy=Find(y);
if(fx!=fy)
{
fa[fy]=fx;
if(vis[fx]||vis[fy])//存在环,把标记转移到祖先
vis[fx]=1,vis[fy]=0;
}
else vis[fx]=1;//祖先标记联通块
}
int ans=len;
for(int i=1;i<=len;i++)
{
if(fa[i]==i&&!vis[i]) ans--;
}
printf("Case #%d: %d
",++cas,ans);
}
return 0;
}
解法2:
将每组 (a_i,b_i) 两个数连上一条边,这道题就可以转化成一个图的问题:给定一张点权图,对于图中每条边可以选择两个顶点中的一个,求最多可以选多少个值不同的点。显然,所有的 (a_i) 与 (b_i) 所构成的图,肯定是由若干连通的块组成的。对每个连通块考虑,设一个连通块有 (n) 个点,它最少有 (n-1) 条边,在这种情况下,最多可以选的点数是 (n-1) 个;而在边多于 (n-1) 时,我们就可以选全部的 (n) 个点。如果把这个图想象成一个有根树,在边数大于 (n-1) 条时,我们在多出的边中随便选一条,在这条边定一个根,就可以选根了。
综上,我们这个问题就转化为了:
在每个连通图中,边的数量大于 (n-1) 条就选 (n) 个,否则选 (n-1) 个,统计所有图之和。