CF600E Lomsat gelral——线段树合并/dsu on tree
题目描述
一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。
这个题意是真的窒息。。。具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数(可能有多个),比如子树中有$3个2,3个1,3个5,那么2,1,5都是众数,答案为2+1+5=8$。
思路
做法一:
线段树合并。权值线段树覆盖颜色$1->100000,用sum$表示颜色最多出现的次数,$ans$表示答案。分$3种情况pushup$即可。
- 左右子树$sum$相等
- 左边$>$右边
- 左边$<$右边
$dfs的时候merge$一下即可。
code
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define smid (l+r>>1)
#define I inline
using namespace std;
typedef long long LL;
const int N=;
LL col[N];
LL maxcol;
int n;
int ls[N*],rs[N*],cnt,rt[N];
LL sum[N*],ans[N*];
vector<int>g[N];
LL out[N]; I void pushup(int now)
{
if(sum[ls[now]]==sum[rs[now]])
{
sum[now]=sum[ls[now]];
ans[now]=ans[ls[now]]+ans[rs[now]];
}
else if(sum[ls[now]]<sum[rs[now]])
{
sum[now]=sum[rs[now]];
ans[now]=ans[rs[now]];
}
else
{
sum[now]=sum[ls[now]];
ans[now]=ans[ls[now]];
}
} I void modify(int &now,int l,int r,int pos)
{
if(!now)now=++cnt;
if(l==r)
{
sum[now]++;ans[now]=l;
return;
}
if(pos<=smid)modify(ls[now],l,smid,pos);
else modify(rs[now],smid+,r,pos);
pushup(now);
} I int merge(int x,int y,int l,int r)
{
if(!x||!y)return x+y;
if(l==r)
{
sum[x]+=sum[y];ans[x]=l;
return x;
}
ls[x]=merge(ls[x],ls[y],l,smid);
rs[x]=merge(rs[x],rs[y],smid+,r);
pushup(x);
return x;
} I void dfs(int u,int fa)
{
for(int i=;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa)continue;
dfs(v,u);
merge(rt[u],rt[v],,);
}
modify(rt[u],,,col[u]);
out[u]=ans[rt[u]];
} int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=;i<=n;i++)
{
cin>>col[i];
rt[i]=i;cnt++;
}
for(int i=;i<n;i++)
{
int x,y;cin>>x>>y;
g[x].push_back(y);g[y].push_back(x);
}
dfs(,);
for(int i=;i<=n;i++)
{
cout<<out[i]<<" ";
}
}
洛谷上交不了,必须到$CF$上交,但是$CF$上不给用scanf("%lld"),就加了$cin$加速。
做法二:
树上启发式合并。这里当做板子题来讲。$dsu on tree$是个啥?其实就是优化的暴力,对于一棵树,我们定义节点$u的重儿子son[u]为其size$最大的儿子,其余为轻儿子。这个算法主要用于:
- 只有对子树的询问
- 没有修改操作
回到这个题目:首先我们考虑暴力$dfs$:遍历每个节点的子树,统计颜色出现的个数,得出当前的的答案,再清空当前点的影响,继续$dfs$,这个算法是$O(n^2)$的,于是我们使用一些重链剖分的性质,搞一个树上启发式合并。具体流程如下:
- $dfs$遍历每个节点
- 先递归所有轻儿子,跑到底层,不保留这一次$dfs$的答案
- 递归重儿子,保留这一次$dfs$的答案
- 重儿子所在子树被处理完了,而且又保留了答案,只剩下当前节点的轻儿子了
- 暴力统计所有轻儿子所在子树的答案
- 通过上面两步得出当前点的答案
- 如果是轻儿子就清空当前点对答案的影响
主体框架

code(比较板子)
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
#define I inline
using namespace std;
const int N=;
typedef long long LL;
int sz[N],son[N],n,col[N],Son;
vector<int>g[N];
LL cnt[N],mx,ans[N],sum; I int read()
{
int x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getchar();}
return x*f;
} I void gets(int u,int fa)
{
sz[u]=;
for(int i=;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa)continue;
gets(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]])son[u]=v;
}
} I void add(int u,int fa,int val)
{
cnt[col[u]]+=val;
if(mx==cnt[col[u]])sum+=LL(col[u]);
if(mx<cnt[col[u]])sum=col[u],mx=cnt[col[u]];
for(int i=;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa||v==Son)continue;
add(v,u,val);
}
} I void dfs(int u,int fa,bool opt)//opt为是否保留答案
{
for(int i=;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa||v==son[u])continue;
dfs(v,u,);//递归处理所有轻儿子
}
if(son[u])dfs(son[u],u,),Son=son[u];
//处理所有重儿子并得到重儿子所在子树答案
add(u,fa,);//得到轻儿子所在子树的答案
Son=;//注意这里,如果要消除影响,重儿子的影响也要消除
ans[u]=sum;//得出答案
if(!opt)add(u,fa,-),sum=,mx=;//消除影响,看情况要不要加add函数
} int main()
{
n=read();
for(int i=;i<=n;i++)col[i]=read();
for(int i=;i<n;i++)
{
int x=read(),y=read();
g[x].push_back(y);g[y].push_back(x);
}
gets(,);
dfs(,,);
for(int i=;i<=n;i++)printf("%lld ",ans[i]);
}
CF600E Lomsat gelral——线段树合并/dsu on tree的更多相关文章
- CF600E:Lomsat gelral(线段树合并)
Description 一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. Input 第一行一个$n$.第二行$n$个数字是$c[i]$.后面$n-1$ ...
- codeforces 600E . Lomsat gelral (线段树合并)
You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's cal ...
- CodeForces600E Lomsat gelral 线段树合并
从树上启发式合并搜出来的题 然而看着好像线段树合并就能解决??? 那么就用线段树合并解决吧 维护\(max, sum\)表示值域区间中的一个数出现次数的最大值以及所有众数的和即可 复杂度\(O(n \ ...
- bzoj3307雨天的尾巴(权值线段树合并/DSU on tree)
题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 1.线段树合并 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少 ...
- 【UOJ#388】【UNR#3】配对树(线段树,dsu on tree)
[UOJ#388][UNR#3]配对树(线段树,dsu on tree) 题面 UOJ 题解 考虑一个固定区间怎么计算答案,把这些点搞下来建树,然后\(dp\),不难发现一个点如果子树内能够匹配的话就 ...
- codeforces600E Lomsat gelral【线段树合并/DSU】
第一次AC这道题,是三年前的一个下午,也许晚上也说不定.当时使用的\(DSU\) \(on\) \(tree\)算法,如今已经淡忘,再学习新的算法过程中,却与旧物重逢.生活中充满不可知会的相遇,即使重 ...
- CF600E Lomsat gelral 树上启发式合并
题目描述 有一棵 \(n\) 个结点的以 \(1\) 号结点为根的有根树. 每个结点都有一个颜色,颜色是以编号表示的, \(i\) 号结点的颜色编号为 \(c_i\). 如果一种颜色在以 \(x\) ...
- CF600E Lomsat gelral 【线段树合并】
题目链接 CF600E 题解 容易想到就是线段树合并,维护每个权值区间出现的最大值以及最大值位置之和即可 对于每个节点合并一下两个子节点的信息 要注意叶子节点信息的合并和非叶节点信息的合并是不一样的 ...
- CF600E Lomsat gelral (线段树合并)
相当于是线段树合并的模板题,比(雨天的尾巴)还要板. 唯一注意的是线段树的更新,因为同一子树中可能有多种颜色占主导地位,要输出编号和,比如一颗子树中,1出现3次(最多),3出现3次,那么应该输出4. ...
随机推荐
- Linux入门(历史与现状)
Linux 入门之 历史与现状 Linux是一个计算机的操作系统,与windows类似,是一款系统软件.操作系统首先是一个计算机程序,使用计算机语言开发,比如C语言.VC语言.是计算机硬件和应用软 ...
- python编程基础之二十四
函数: def 函数名([参数1],[参数2],[参数3], ... ,[参数n]): 函数体代码 函数名命名规则:同标识符命名相同,但是多了一点,不要和系统函数重名,其实所有命名都是一样只要符合标识 ...
- 【TencentOS tiny】深度源码分析(6)——互斥锁
互斥锁 互斥锁又称互斥互斥锁,是一种特殊的信号量,它和信号量不同的是,它具有互斥锁所有权.递归访问以及优先级继承等特性,在操作系统中常用于对临界资源的独占式处理.在任意时刻互斥锁的状态只有两种,开锁或 ...
- mac 下修改 jenkins 端口以及Jenkins的启动、关闭与更新
安装包安装的Jenkins修改默认端口的方法: 先关闭jenkins ; 命令行下修改端口:sudo defaults write /Library/Preferences/org.jenkins-c ...
- linux 防火墙基本使用
写在最前面 由于工作后,使用的Linux就是centos7 所以,本文记录是是centos7的防火墙使用. 从 centos7 开始,系统使用 firewall 进行防火墙的默认管理工具. 基本使用 ...
- 最简单的ArcGIS Engine应用程序(上)
名词: IWorkspaceFactory 工作空间工厂 ShapeFileWorksapceFactory 矢量文件工作空间工厂 IWorkspce 工作空间 IFeatrueWorkspace 要 ...
- Ubuntu18.04安装NVIDIA显卡驱动
1. 查看GPU型号 lspci | grep -i nvidia 我是 GeForce GTX 960M 2. NVIDIA官网下载驱动: https://www.nvidia.com/Downlo ...
- Neo4j:图数据库GraphDB(一)入门和基本查询语句
图数据库的代表:Neo4j 官网: http://neo4j.com/ 引言:为什么使用图数据库 在很多新型项目中,应用图数据库已经是势在必行的趋势了,因为图数据库可以很好的表示各种节点与关系的概念 ...
- ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库
1. 前言 前面的博文中,无论是作为client端还是server端,它们之间的通信都是通过具体的IP地址来寻址.通过IP地址来寻址,本身就是一个弊端,用户怎么会去记住这些魔法数字呢?那么有没 ...
- Linux及Windows安装Redis(详细)
Linux及Windows安装Redis 1.Windows安装教程 1.1下载 https://github.com/MSOpenTech/redis/releases 进入github里下载red ...