【题解】Lomsat gelral [CF600E]

【题解】Lomsat gelral [CF600E]

【前言】

写完 \(\text{Dsu on tree}\) 后大致浏览了网上的题解,常见做法有以下几种:

  • \(\text{Dsu on tree}\)(占大多数,毕竟是板子)
  • 线段树合并(空间巨大)
  • \(O(n\sqrt{n}logn)\) 的 \(\text{DFS}\) 序 \(+\) \(sb\) 暴力莫队(时间巨大)
  • \(O(n\sqrt{n})\) 的 \(\text{DFS}\) 序 \(+\) 回滚莫队(效率一般)

但就是没找到一篇 \(\text{DFS}\) 序 \(+\) 分治,让我来做全网第一篇吧(只是我没有找到,希望不要打脸吧)。

【分析】

子树查询转到序列上后其本质是一系列的区间查询,我们知道,要想用分治是需要满足一定单调性的,那么这些区间是否有我们想要的性质呢?

先给出一些定义:

  • \(size(x)\) 表示 \(|subtree(x)|\),即以 \(x\) 为根的子树大小。
  • \(dfn(x)\) 表示节点 \(x\) 的 \(\text{DFS}\) 序。
  • \(idx(i)\) 表示 \(\text{DFS}\) 序 \(i\) 所对应的节点编号,满足 \(dfn(idx(i))=i,\ idx(dfn(x))=x\)。
  • \(Rdfn(i)\) 表示 \(i+size(idx(i))-1\),即 \(\text{DFS}\) 序 \(i\) 所对应的的节点子树中最大的 \(\text{DFS}\) 序。

【引理】

抛结论
设 \(i'<i\),若存在一个 \(j\) 满足 \(i \leqslant j \leqslant Rdfn(i)\) 且 \(i' \leqslant j \leqslant Rdfn(i')\),那么 一定有 \(Rdfn(i') \geqslant Rdfn(i)\) 。
(按照 \(\text{YudeS}\) 楪 巨佬所说,穿过 \(j\) 的区间有互相包含的关系,放在此处即是区间 \([i,Rdfn(i)]\) 被包含于 \([i',Rdfn(i')]\) 中 )。

其实很简单,稍想一下就明白了。

证明
由给出的两个条件可知 \(idx(j) \in subtree(idx(i))\) 且 \(idx(j) \in subtree(idx(i'))\),所以 \(idx(i),idx(i')\) 均为\(idx(j)\) 的祖先节点。
又因为 \(i'<i\),且任意一个节点 \(x\) 的所有祖先都在从根到 \(x\) 的简单路径上,所以 \(idx(i')\) 应为 \(idx(i)\) 的祖先节点,于是有 \(Rdfn(i') \geqslant Rdfn(i)\)。

【算法实现】

回到这道题,有了上面那个性质,用分治已经很显然了吧,对于一层 \((L,mid,R)\) 扫一遍计算出:满足 \(L \leqslant i \leqslant mid\) 且 \(mid+1 \leqslant Rdfn(i) \leqslant R\) 的所有 \(ans[i]\) 。

时间复杂度为:\(O(nlogn)\)。

核心操作就一个分治函数,好想又好写,居然没人用....

像这种 \(dot\) 的题应该都可以用 \(\text{DFS}\) 序 \(+\) 回滚莫队/分治 搞吧,\(insert\) 函数都不需要改,时间复杂度和分治基本一样(蒟蒻瞎口胡,可信度极低

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+3;
int o,n,m,x,y,t,tmp,dfn_O,A[N],Q[N],cnt[N],dfn[N],idx[N],size[N],head[N];LL ans,Ans[N];
struct QAQ{int to,next;}a[N<<1];
inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void dfs(Re x,Re fa){//预处理dfn序
    idx[dfn[x]=++dfn_O]=x,size[x]=1;
    for(Re i=head[x],to;i;i=a[i].next)
        if((to=a[i].to)!=fa)dfs(to,x),size[x]+=size[to];
}
inline void CL(){//清空贡献,从zero开始
    while(t)cnt[Q[t--]]=0;ans=tmp=0;
}
inline void insert(Re x){//加入点x的贡献
    ++cnt[Q[++t]=A[x]];
    if(cnt[A[x]]>tmp)tmp=cnt[ans=A[x]];
    else if(cnt[A[x]]==tmp)ans+=A[x];
}
inline void sakura(Re L,Re R){//分治解决(L,R)
    if(L==R){if(size[L]==1)Ans[L]=A[L];return;}
    Re mid=L+R>>1;
    sakura(L,mid),sakura(mid+1,R);//递归解决下面的
    Re p=mid;CL();//搞一个指针p
    for(Re i=mid,j;i>=L&&(j=i+size[idx[i]]-1)<=R;--i){//当j=Rdfn(i)大于R时就可以结束了
        insert(idx[i]);
        if(j<=mid)continue;//只解决对于j>mid的部分
        while(p<j)insert(idx[++p]);//由性质可知满足大于mid的那部分j是单调递增的,不断移动指针p即可
        Ans[idx[i]]=ans;//获得答案
    }
}
int main(){
//  freopen("123.txt","r",stdin);
    in(n),m=n-1;
    for(Re i=1;i<=n;++i)in(A[i]);
    while(m--)in(x),in(y),add(x,y),add(y,x);
    dfs(1,0),sakura(1,n);
    for(Re i=1;i<=n;++i)printf("%lld ",Ans[i]);
}

【参考资料】

原文地址:https://www.cnblogs.com/Xing-Ling/p/12336327.html

时间: 2024-11-09 23:34:21

【题解】Lomsat gelral [CF600E]的相关文章

【CF600E】 Lomsat gelral

CF600E Lomsat gelral Solution 考虑一下子树的问题,我们可以把一棵树的dfn序搞出来,那么子树就是序列上的一段连续的区间. 然后就可以莫队飞速求解了. 但是这题还有\(\Theta(nlog_n)\)的做法.能有\(\Theta(n\sqrt{n})\)的做法要什么\(logn\)的 考虑\(dsu\ on\ tree\),与莫队没有任何区别. 如果不会的话,请自行跳转小Z的袜子并且切掉. 代码实现 #include<stdio.h> #include<std

Codeforces 600E Lomsat gelral (Dsu On the Tree)

题目链接 Lomsat gelral 占坑--等深入理解了再来补题解-- 1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 #define rep(i, a, b) for (int i(a); i <= (b); ++i) 6 7 typedef long long LL; 8 9 const int N = 600010; 10 11 int n; 12 int cc[N], col[N], sz[N], son[N];

CF 600 E. Lomsat gelral

E. Lomsat gelral http://codeforces.com/contest/600/problem/E 题意: 求每个子树内出现次数最多的颜色(如果最多的颜色出现次数相同,将颜色编号求和). 分析: dsu on tree. 这个可以解决一系列不带修改的子树查询问题. 考虑暴力的思路:就是枚举每个子树,计算每个颜色出现的个数.统计答案. dsu on tree:最后一个子树枚举计算完了,它的贡献可以保留,然后用其它的子树去合并.(最后一棵子树是最大的).现在的复杂度就是nlog

CF600E Lomsat gelral 【线段树合并】

题目链接 CF600E 题解 容易想到就是线段树合并,维护每个权值区间出现的最大值以及最大值位置之和即可 对于每个节点合并一下两个子节点的信息 要注意叶子节点信息的合并和非叶节点信息的合并是不一样的 由于合并不比逐个插入复杂度高,所以应是\(O(nlogn)\)的 #include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #i

[CF600E]Lomsat gelral

题意翻译 一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 线段树合并板子题,没啥难度,注意开long long 不过这题$dsu$ $on$ $tree$确实更快 代码: 1 #include<iostream> 2 #include<cstdio> 3 #define ls ch[node][0] 4 #define rs ch[node][1] 5 #define M 100010 6 using namespace std;

CF600E Lomsat gelral(线段树合并)

link 题目大意:给以1为根的一棵树,求树上每个点子树中出现次数最多的权值(如果有多个就求他们的和) 对每个点开一个线段树维护子树内权值的桶,dfs时候线段树合并就行了. 因为最后线段树一共插入最多 \(O(n\log n)\) 个节点,每个节点最多会被合并一次,所以复杂度是 \(O(n\log n)\) 的. #include <cstdio> #include <vector> using namespace std; struct fuck { int maxval; lo

【CF600E】Lomsat gelral——树上启发式合并

(题面来自luogu) 题意翻译 一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. ci <= n <= 1e5 树上启发式合并裸题.统计时先扫一遍得到出现次数最大值,然后再扫一遍看哪个颜色的出现次数与mxCnt相等.注意用一个bool数组判重,清空轻儿子贡献时要顺手把bool数组也打成false. 代码: #include <iostream> #include <cstdio> #include <cctype&

CodeForces 600E Lomsat gelral(线段树合并)

题目链接:http://codeforces.com/problemset/problem/600/E You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the

Codeforces 600E. Lomsat gelral(Dsu on tree学习)

题目链接:http://codeforces.com/problemset/problem/600/E n个点的有根树,以1为根,每个点有一种颜色.我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多.求占领每个子树的所有颜色之和. 我们都知道可以$BST$启发式合并从而完美${O(nlogn^{2})}$,这太丑陋了. 那么$Dsu~~on~~tree$是在干啥呢? 找出树中每一个节点的重儿子,统计答案的时候优先进入每一个点的所有轻儿子,之后再进入重儿子,目的是保留重儿子所