题解
暴力:遍历所有点为根节点的情况,然后遍历子节点,统计众数,时间复杂度 \(O(n^2)\)
优化:上面的算法时间复杂度之所以为 \(O(n^2)\) 是因为算父节点时,子节点又重新算了一遍,所以我们可以在算父节点时,保留一个子树的贡献,然后其他子树的贡献暴力遍历一遍
运用重链剖分,每次保留重儿子所在的子树的贡献,遍历轻儿子所在子树所有点的贡献,时间复杂度来到了 \(O(nlogn)\),
证明:
令轻儿子的“上边”为轻边,则每有一个轻边,都要遍历一遍其子树内的所有元素
所以节点 \(u\) 被暴力计算贡献的次数为根节点到 \(u\) 的路径上,轻边的个数
因为轻边所在子树大小小于父节点所在子树大小的一半,所以根节点到任意节点的路径上,轻边数量不超过 \(logn\)
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dfn[100005];
ll sizes[100005];
vector<ll> G[100005];
ll bigson[100005];//代表编号
ll cnt=0;
ll haxi[1000005];
void dfs1(ll now,ll fa)
{
dfn[now]=++cnt;
haxi[cnt]=now;
sizes[now]=1;
ll maxs=0;
for(auto next:G[now])
{
if(next==fa) continue;
dfs1(next,now);
sizes[now]+=sizes[next];
if(sizes[next]>maxs)
{
bigson[now]=next;
maxs=sizes[next];
}
}
}
ll tot=0;//出现不同颜色数
ll line=0;//最大出现次数
ll ans=0;//贡献
ll sum[100005]={0};//维护每个颜色出现的次数
ll st[100005]={0};//维护出现过的颜色的编号,算是离散化
void cancel()
{
while(tot) sum[st[tot--]]=0;
line=0;
ans=0;
}
ll c[100005];
ll res[1000005];
void add(ll id)
{
if(!sum[c[id]]) st[++tot]=c[id];
sum[c[id]]++;
if(sum[c[id]]>line)
{
ans=c[id];
line=sum[c[id]];
}
else if(sum[c[id]]==line) ans+=c[id];//由于颜色出现统计只有清零和增加两种,所以ans要么变大要么为零
}
void dfs2(ll now)
{
for(auto next:G[now])
{
if(dfn[next]<dfn[now]||next==bigson[now]) continue;
dfs2(next);
cancel();//取消其贡献。为什么要取消?为了清空计算其他子树
}
if(bigson[now])
{
dfs2(bigson[now]);
}
for(auto next:G[now])
{
if(dfn[next]<dfn[now]||next==bigson[now]) continue;
for(ll i=dfn[next];i<=dfn[next]+sizes[next]-1;i++)
{
add(haxi[i]);//加上其贡献
}
}
add(now);
res[now]=ans;
}
void solve()
{
ll n;
cin>>n;
for(ll i=1;i<=n;i++) cin>>c[i];
for(ll i=1;i<n;i++)
{
ll x,y;
cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs1(1,1);
dfs2(1);
for(ll i=1;i<=n;i++) cout<<res[i]<<' ';
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll t=1;
//cin>>t;
while(t--) solve();
return 0;
}
标签:ll,next,gelral,sum,节点,now,id,Lomsat
From: https://www.cnblogs.com/pure4knowledge/p/18298308