话说之前一直是一个一个Day地写题解,但由于蒟蒻在短期内大量被J,而且题目也不能全数解决,以后都一题一题地写题解了
Problem
题面超长,不建议看题面,像我这种语文得D的废物选手根本看不懂
大意:给定一棵树与每个点的权值,一种合法的点权方案指使得父节点权值为所有儿子节点之和,兄弟节点之间点权相等,并求所有合法点权方案中,与原点权冲突最少的方案(配上样例看看,题意八九不离十)
Solution
这题算是HNOI中比较容易的一道题了
首先明确:最优方案至少保留一个点不便(显然)
模拟下样例发现当一个点权值确定不变时,整棵树都可以推导而得(想一想就知道了)
然后一种基于这种思想的暴力就是枚举哪一个点不变,看看有多少点不变,复杂度 O(n2) O ( n 2 )
然后发现其实很多枚举都是重复的,简化一下:由于一个点确定,整棵树确定,所以只要看看各个点分别确定时根的值即可
考虑快速求解一个点确定时根的取值,发现对于一个节点x(非根)的值不变时,它父亲的值一定是x的权值乘上x父亲的儿子数(因为它的兄弟的权值都相等)
所以x的值确定时根的取值是从根到x路径上所有点的儿子数乘积乘上该点权值
可以从根开始 O(n) O ( n ) 预处理根到x路径上的儿子数乘积,得到n个值乘上该点权值,得到的就是每个点分别确定时根的权值,统计根中相同权值最多的数字即可
对于大量数字相乘,在OI中一般可以采用取对数的方法
x∗y=x∗y⇔lg(x∗y)=lgx+lgy
x
∗
y
=
x
∗
y
⇔
lg
(
x
∗
y
)
=
lg
x
+
lg
y
(不会的去看高中人教版数学必修1)
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define rg register
#define cl(x) memset(x,0,sizeof(x))
#define cl1(x) memset(x,-1,sizeof(x))
#define eps (1e-10)
template <typename _Tp> inline _Tp read(_Tp&x){
rg char c11=getchar(),ob=0;x=0;
while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')c11=getchar(),ob=1;
while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}
const int N=500500;
struct Edge{int v,nxt;}a[N<<1];
int head[N],tot[N],n,_(0);
double f[N],g[N],d[N];
inline void add(int u,int v){a[++_].v=v,a[_].nxt=head[u],head[u]=_;}
inline void dfs(int x,int fa){
for(rg int i=head[x];i;i=a[i].nxt)
if(a[i].v!=fa)
f[a[i].v]=f[x]+log(tot[x]),
dfs(a[i].v,x);
return ;
}
int main(){
read(n);
for(rg int i=1;i<=n;++i)read(d[i]);
cl1(tot);tot[1]=0;
for(rg int i=1,x,y;i<n;++i)
read(x),read(y),add(x,y),add(y,x),++tot[x],++tot[y];
f[1]=log(1);
dfs(1,0);
for(rg int i=1;i<=n;++i)
g[i]=f[i]+log(d[i]);
sort(g+1,g+n+1);
int cnt(1),ans(0);
for(rg int i=2;i<=n;++i)
if(g[i]-g[i-1]<eps)++cnt;
else ans=max(ans,cnt),cnt=1;
ans=max(ans,cnt);
printf("%d\n",n-ans);
return 0;
}