随机生成树
链接:https://www.nowcoder.com/acm/contest/179/B
题目描述
牛牛在纸上画了N个点(从1到N编号),每个点的颜色用一个整数描述。牛牛决定用这N个点随机生成一棵树,生成的规则如下:
1、1号点是根节点
2、对于2号点到N号点,每个点随机指定一个父亲。i号点(2 <= i <= N)的父亲在i的约数中随机挑选一个。(例如10号点的父亲可以是1号,2号,5号,7号点的父亲只能是1号点)
树生成完之后,牛牛可以计算出这个树有多少个联通块,一个联通块是一些点的集合,需要满足以下两个条件:
1、从集合中任取两个点都满足:两个点颜色相同,且这两个点之间存在一条树边组成的路径,路径上的所有点都和这两个点颜色相同
2、对于集合中的任意一个点和集合外的任意一个点:两点要么不同色,要么不存在一条树边组成的路径使得路径上所有点都和这两个点同色。
牛牛希望计算出生成的树中最多有多少个联通块
输入描述:
第一行一个整数N代表点数
第二行N个整数,第i个整数ci代表第i个点的颜色
对于30%的数据, 2 <= N <= 10, 1 <= 颜色 <= 5
对于60%的数据, 2 <= N <= 5000, 1 <= 颜色 <= 5000
对于80%的数据, 2 <= N <= 200000, 1 <= 颜色 <= 10^9
对于100%的数据, 2 <= N <= 500000, 1 <= 颜色 <= 10^9
输出描述:
输出一个整数最多有多少联通块
………………………………………………………………………………………………………………………
开始一直思维混乱,想贪心一下先连边再计算联通块,但并不保证正确性。
题解倒了一下思路,并且只考虑当前结点及其父节点。
考虑断开两端颜色不同的边,联通块数等于剩下的森林中树的数目。
一开始有一个联通块,每断掉一条边会增加一个联通块。
因此联通块数 = 1 + (颜色和父亲不同的点数)。
只需考虑每个点是否可以和父亲颜色不同即可。
再加一些数学优化
依次枚举每个数的约数,可以直接枚举到根号,复杂度nsqrt(n),但常数很小,可以通过
正解是对于每个数枚举它的倍数,这样复杂度为
N + N / 2 + N / 3 + N / 4 + … + N / N
(调和级数)
复杂度为O(NlogN)
然后就很妙了……
#include<cstdio>
using namespace std;
int n,ans,col[500005];
bool vis[500005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&col[i]);
for(int i=1;i<=n;i++){
for(int j=1;j*i<=n;j++){
if(col[i]!=col[i*j]&&!vis[i*j])ans++,vis[i*j]=1;
}
}
printf("%d",ans+1);
return 0;
}