题目描述
输入描述:
输出描述:
示例1
输入
2
6
1 2
2 3
3 4
1 4
1 3
2 4
5
1 2
1 2
1 3
2 3
5 6
输出
Case #1: 4
Case #2: 4
题目大意
给定 n n n组数,每组数有 a i , b i a_i,b_i ai,bi。你在每组数中都可以选择一个数,且只能选一个,这个数要求之前没有选到过。求你在这 n n n组数中选的数最多有多少。
分析
我们理解题意后,要知道对于每一个数,选不选有两个限制,如果这个数同一组的另一个数选了,那么这个数不能选。如果所有组中与这个数相等的选了,那么这个数也不能选。
错的思路
首先是小胖在肝这题,他采用的思路是贪心。这一听就是不对的。
不如先听听他的思路。他首先枚举每一组,然后如果这一组中有一个数被选了,那么就选另一个数。如果两个数都没有被选过,那么选出现次数少的那个。
这乍一听是没有问题的,但是很容易就会被hack掉。比如:
6
1 3
2 4
5 6
1 4
2 5
3 6
这组,就能随便怼。
每个数出现次数一样,那么13的时候选1。然后24选2,56选5。
然后14选4,25没得选,36选6。选到了5个数。但是显然,我分别选126453就可以选6个。
所以这题是不能贪心的。同样的动规不能用,因为状态太多了,更不能爆搜,肯定是
T
L
E
TLE
TLE。
对的思路
x
i
n
j
u
n
xinjun
xinjun说过,当你做签到题走投无路的时候,想想建图。
???你也许会和我一样???小问号你是否有很多朋友???
什么,为什么这也能建图???
别慌,我们来回顾一下小胖,他在WA了数遍之后,提出了一个更大胆的想法,在数字种类和n里面取个最小值。不用说,这也是一个随便hack的。但是我们沿着这个接着想,数字的种类啊
…
\dots
…
我们考虑一下之前说每个数的限制,同样的数不能选,那么我就把所有同样的数给同一个点,也就是说,我拿它的数值当节点编号。由于同一组的数不能都选,那么我就把每组数连一条边。
诶这样就可以很好地体现其限制条件了。
那么接下来怎么办呢?首先我们同样的数只能选一个可以转化为我们在图上选节点就可以解决。然后就是考虑同一组数,很容易想到,我每次选的时候,都选一个点,然后顺带删除与之相连的一条边就可以了。所以我们就是要求能取多少个这样的点。
继续深入,如果这是一个有环的图,那么 n n n个点至少有 n n n条边,所以肯定是能选择所有的点。如果是一棵树,那么会有一个节点无法选择。那么问题就简化为,一个图集合里,求有多少个树。
一些细枝末节的东西
- 答案怎么算 我们发现如果全是有环图,那么都可以选择,有一棵树,就少一个节点,所以我们可以把答案初值为不同的数字个数,然后找到一棵树,就减一即可。
- 怎么判断树 利用树的性质,有 n − 1 n-1 n−1条边。考虑用并查集带权维护一下每个图的度的和,以及图中的点的个数,那么如果点的个数 − 1 -1 −1的两倍等于度的话,它就是树。因为每条边可以贡献两个度。
- 数字太大了 可以离散化。具体见代码。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+100;
int f[MAXN],a[MAXN],b[MAXN],l[MAXN],d[MAXN],sz[MAXN];
//f 并查集的祖先 l 离散化的集成数组 d 计算度 sz 每个图中的点数
int find(int x){
if(f[x]==x) return x;
int fa=f[x];
f[x]=find(f[x]);
d[x]+=d[fa];
sz[x]+=sz[fa];
return f[x];
}//查找祖先
int main()
{
int t,n,m,lll,ans;
scanf("%d",&t);
for(int ii=1;ii<=t;ii++){
scanf("%d",&n);m=0;
memset(l,0,sizeof(l));
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
l[++m]=a[i];
l[++m]=b[i];
}
memset(d,0,sizeof(d));
sort(l+1,l+1+m);
lll=unique(l+1,l+1+m)-l-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(l+1,l+1+lll,a[i])-l;
b[i]=lower_bound(l+1,l+1+lll,b[i])-l;
d[a[i]]++;d[b[i]]++;//顺带算度
}
//离散化
for(int i=1;i<=lll;i++) f[i]=i,sz[i]=1;//并查集的初始化
for(int i=1;i<=n;i++){
int fa=find(a[i]),fb=find(b[i]);
if(fa!=fb){
sz[fa]+=sz[fb];
d[fa]+=d[fb];
f[fb]=fa;
}//合并
//这里WA死了,又是好久不打并查集的锅
}
ans=lll;
for(int i=1;i<=lll;i++)
if(find(i)==i&&sz[i]*2-2==d[i])
ans--;//判断一个图是否为树
printf("Case #%d: %d\n",ii,ans);
}
}
END
要注意并查集的时候,带权的话要是祖先之间合并。
x i n j u n xinjun xinjun太强了,10分钟打出了我们debug3小时的代码。