https://www.luogu.com.cn/problem/CF600E
题意翻译
- 有一棵 nn 个结点的以 11 号结点为根的有根树。
- 每个结点都有一个颜色,颜色是以编号表示的, ii 号结点的颜色编号为 c_ici。
- 如果一种颜色在以 xx 为根的子树内出现次数最多,称其在以 xx 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
- 你的任务是对于每一个 i\in[1,n]i∈[1,n],求出以 ii 为根的子树中,占主导地位的颜色的编号和。
- n\le 10^5,c_i\le nn≤105,ci≤
思路:最暴力直接的想法也就是O(n^2)的dfs暴力。先考虑对于一个节点,怎么算他的子树的编号和答案。对其dfs先序遍历下去(将这个dfs记为cal函数)。那么对于一颗树全部的节点,在dfs中从上面枚举点进行cal也好,从下面枚举点进行cal也好,都可以。如果想优化每次进行记录的节点信息,可以开个一维数组,跑完cal后记录,然后清空再跑。然而二维数组不用清空,但是开出来是1e5*1e5是MLE的。
那么怎么优化呢?刚才提到dfs中枚举点可以从上面开始cal,也可以从下面开始cal,那么dsu on tree从最底下开始cal达到优化成O(nlogn)的目的。
以图来表示这个dsu on tree 的优化过程。
其中标红色的是重儿子。
模拟一下算法吧
开始先通过dfs找到最底层的儿子,所以开始找到3,然后3没有重儿子,此时对3进行cal,那么此时ans[3]的答案就已经统计出来了。这个时候我们删除在cal(3)过程中保存的子树信息(因为二维空间不够,一维不清空会对其他节点的统计造成影响)
然后这时候2还有重儿子,我们进入4,cal(4),获得了ans[4]的答案,这个时候我们通过dfs的标记keep不删除这个4子树的贡献。
然后这时候我们dfs递归栈退到了2节点。此时cal(2),我们通过flag标记,cal(2)的时候只再去暴力统计一遍3子树,而之前不删除的贡献就是4子树的贡献,直接加上去。这样子ans[2]的答案就统计完了。然后看到2节点并不是重儿子,所以还是要暴力清空。
那么对5的操作和2是一样的。最后到8,暴力cal(8),不清空,然后dfs栈回到1,这时候1再去暴力跑cal(2)和cal(5),加上之前没有删除的8节点的子树。
时间复杂度O(nlogn).证明百度吧(和树链剖分有关)
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
LL col[maxn],sum,maxc,cnt[maxn];
LL siz[maxn],son[maxn];///度数,重儿子
LL ans[maxn];
LL flag=-1;
vector<LL>g[maxn];
void predfs(LL u,LL fa)
{
siz[u]=1;
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(v==fa) continue;
predfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]){
son[u]=v;
}
}
}
void cal(LL u,LL fa,LL val)
{
cnt[col[u]]+=val;//val为正为负可以控制是增加贡献还是删除贡献
if(cnt[col[u]]>maxc){
maxc=cnt[col[u]];
sum=col[u];
}
else if(cnt[col[u]]==maxc){
sum+=col[u];
}
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(v==fa||v==flag) continue; //不能写if(v==f||v==son[u])
cal(v,u,val);
}
}
void dfs(LL u,LL fa,bool keep)
{
//* 第一步:搞轻儿子及其子树算其答案删贡献
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(v==fa||v==son[u]) continue;
dfs(v,u,0);
}
//* 第二步:搞重儿子及其子树算其答案不删贡献
if(son[u]){
dfs(son[u],u,1);
flag=son[u];
}
//* 第三步:暴力统计u及其所有轻儿子的贡献合并到刚算出的重儿子信息里
cal(u,fa,1);
flag=0;///别忘了
//* 把需要删除贡献的删一删
ans[u]=sum;
if(keep==0){
cal(u,fa,-1);
sum=0;//这是因为count函数中会改变这两个变量值
maxc=0;
}
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n;cin>>n;
for(LL i=1;i<=n;i++){
cin>>col[i];
}
for(LL i=1;i<n;i++){
LL u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
predfs(1,-1);
dfs(1,-1,0);
for(LL i=1;i<=n;i++){
cout<<ans[i]<<" ";
}
cout<<endl;
return 0;
}