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. ...
随机推荐
- Win10安装gcc、g++、make
下载安装包: 1. MinGW官网下载:http://www.mingw.org 点击右上角Downloads 点击下载 mingw-get-setup.exe 2. 百度网盘(2 ...
- [USACO10NOV]奶牛的图片Cow Photographs
题目描述 Farmer John希望给他的N(1<=N<=100,000)只奶牛拍照片,这样他就可以向他的朋友炫耀他的奶牛. 这N只奶牛被标号为1..N. 在照相的那一天,奶牛们排成了一排 ...
- GSS3 - Can you answer these queries III
题意翻译 nnn 个数, qqq 次操作 操作0 x y把 AxA_xAx 修改为 yyy 操作1 l r询问区间 [l,r][l, r][l,r] 的最大子段和 感谢 @Edgration 提供的 ...
- 为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?
之前在阅读<阿里巴巴Java开发手册>时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符 ...
- gulp——用自动化构建工具增强你的工作流程
想要使用gulp,就要了解gulp,就像追自己的爱豆,你要知道爱豆的喜好.兴趣 简单的了解你的新爱豆———安装nodejs -> 全局安装gulp -> 项目安装gulp以及gulp插件 ...
- ElasticSearch安装及使用
ElasticSearch安装及使用 ELK由Elasticsearch.Logstash和Kibana三部分组件组成. Elasticsearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置 ...
- git 拉取指定的远程分支(三种方式)
直接拉取 git clone -b ants git@github.com:Ants-double/CareerJava.git git clone -b 远程分支名 仓库地址 本地已经有相关的仓库代 ...
- Java基于回调的观察者模式详解
本文由“言念小文”原创,转载请说明文章出处 一.前言 什么是回调?回调如何使用?如何优雅的使用?本文将首先详解回调的原理,然后介绍回调的基本使用方法,最后介绍基于回调的“观察者模式”实现,演示如何优化 ...
- Hadoop 在 windows 7 64位的配置(一)|非cygwin
参照原文 http://blog.csdn.net/supperman_009/article/details/39991809 环境: Hadoop-2.4.1 Windows 7 64位 jd ...
- UIAlert
转自:https://blog.csdn.net/deng0zhaotai/article/details/53887508 通过uialertcontroller实现三种简易弹框 (一)警告类 - ...