Problem

Description

\(lrb\) 有一棵树,树的每个节点有个颜色。给一个长度为 \(n\) 的颜色序列,定义 \(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量。以及

\[sum[i]=\sum_{j=1}^n s(i,j)
\]

现在他想让你求出所有的 \(sum[i]\)

Input Format

第一行为一个整数 \(n\) ,表示树节点的数量

第二行为 \(n\) 个整数,分别表示 \(n\) 个节点的颜色 \(c[1],c[2]……c[n]\)

接下来 \(n-1\) 行,每行为两个整数 \(x,y\),表示 \(x\) 和 \(y\) 之间有一条边

Output Format

输出 \(n\) 行,第 \(i\) 行为 \(sum[i]\)

Sample

Input

5
1 2 3 2 3
1 2
2 3
2 4
1 5

Output

10
9
11
9
12

Explanation

Explanation for Input

\(sum[1]=s(1,1)+s(1,2)+s(1,3)+s(1,4)+s(1,5)=1+2+3+2+2=10\)

\(sum[2]=s(2,1)+s(2,2)+s(2,3)+s(2,4)+s(2,5)=2+1+2+1+3=9\)

\(sum[3]=s(3,1)+s(3,2)+s(3,3)+s(3,4)+s(3,5)=3+2+1+2+3=11\)

\(sum[4]=s(4,1)+s(4,2)+s(4,3)+s(4,4)+s(4,5)=2+1+2+1+3=9\)

\(sum[5]=s(5,1)+s(5,2)+s(5,3)+s(5,4)+s(5,5)=2+3+3+3+1=12\)

Range

对于 \(40\%\) 的数据,\(n\le 2000\)

对于 \(100\%\) 的数据,\(1\le n,c[i]\le 10^5\)

Algorithm

点分治

Mentality

点分治的神仙题哇天哪,一个个题解看得我那叫一个懵。我还是看神仙的题解才懂的,我这篇题解希望能让您们理解神仙的做法。

首先瞅一眼数据范围 \(10^5......\) 是 \(nlog\) 或 \(nlog^2\) 的标准范围,那么很显然我们不能统计路径,而是应该统计颜色对路径们的贡献。则第一时间发现和树上路径有关,自然上点分。

那么点分怎么搞呢?说来就不简单啊 \(......\) ,对于当前处理的这颗子树,我们记 \(cnt[i]\) 为它的所有子树内到他的路径中包含颜色 \(i\) 的路径条数,那么当处理到一颗子树内的时候,我们统计得到其他子树贡献的所有 \(cnt\) 数组,那么对于该子树内的一个点,先不考虑它到根节点的路径上的颜色,则其它子树内的颜色对当前节点 \(x\) 的贡献肯定为 \(\sum cnt[i]\) (您别告诉我这个看不出来就成) 。

那么这样一来就有问题如下:

  • \(cnt\) 数组如何统计。
  • 当前节点 \(i\) 到根节点路径上的颜色的贡献如何处理。

我们先解决 \(cnt\) 数组,这个很简单,我们先统计每颗子树的 \(cnt\) 数组,设 \(col[i]\) 为点 \(i\) 的颜色,则每当我们访问到一个之前没有出现过的颜色 \(col[i]\) ,那么 \(cnt[col]+=size[i]\) 。我想这个很好理解,因为子树内的每个节点到根节点的路径上都经过了节点 \(i\) 。所以我们只需要开个 \(book\) 数组记录 \(book[i]\) 为当前节点到根节点路径上颜色为 \(i\) 的节点个数,进入 \(i\) 节点的时候 \(book[col[i]]++\) ,退出时 \(--\) 即可。

如何减去当前处理的子树对 \(cnt\) 数组的贡献呢?当然是在处理这颗子树之前再扫一遍,把贡献去掉,处理完之后又扫一遍,再加回来 = = 。我也觉得很蠢。

第二个问题稍稍难想一点,设当前分治的子树为 \(now\) ,我们先 \(dfs\) 统计贡献,对于当前结点 \(i\) ,我们记录 \(tot=\sum_{col[j]\in col[pre_i]} cnt[col[j]]\) ,也就是将 \(i\) 到根节点路径上的颜色在 \(cnt\) 数组中对应贡献记录下来,由于其他子树内这些颜色的路径到点 \(i\) 也会经过这些点,我们不能重复计算贡献,那就只能牺牲 \(cnt\) 数组的贡献了 \(QwQ\) 。

接着,如果统计的过程中遇到了一个新的颜色,那么 \(tot+=cnt[col[i]]\) ,可以预料到的是,这个新颜色对它的子树内每个节点的贡献肯定是 \(size[root]-size[now]\) ,也即当前分治子树外所有节点都可以通过当前节点到达当前节点子树内的节点,我们带着这个贡献往下 \(dfs\) ,设其为 \(num\) ,在设一个 \(sum=\sum cnt[i]\) 来表示总贡献,那么当我们 \(dfs\) 到点 \(i\) 的时候就变成了如下流程:

  • 判断点 \(i\) 的颜色是否出现过,若没有,则 \(tot+=cnt[col[i]],num+=size[root]-size[now]\) 。
  • \(ans[i]+=sum-tot+num\)

然后分治递归处理更多的子树就完了!

不理解可以看下代码(码风仙,无空格,不过有注释)

Code

#include <cstdio>
#include <iostream>
using namespace std;
int n, col[100001];
int rt, sum, top, Y, maxs[100001], size[100001], now[100001], cbook[100001],
cnt[100001];
int head[100001], nx[200001], to[200001];
bool vis[100001], Book[100001];
long long Sum, ans[100001];
void add(int u, int v, int d) {
to[d] = v, nx[d] = head[u];
head[u] = d;
}
void getrt(int x, int fa) {
size[x] = 1, maxs[x] = 0;
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa && !vis[to[i]]) {
getrt(to[i], x);
size[x] += size[to[i]];
maxs[x] = max(maxs[x], size[to[i]]);
}
maxs[x] = max(maxs[x], sum - size[x]);
if (maxs[x] < maxs[rt]) rt = x;
}
void getsize(int x, int fa) {
size[x] = 1;
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa && !vis[to[i]]) getsize(to[i], x), size[x] += size[to[i]];
}
void getcol(int x, int fa) {
if (!now[col[x]]) {
cnt[col[x]] += size[x];
Sum += size[x];
} //如果没出现过,我们的贡献就 +=size
if (!Book[col[x]])
cbook[++top] = col[x],
Book[col[x]] = true; //记录一下当前分治的部分总共有哪些颜色
now[col[x]]++; //出现次数变更
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa && !vis[to[i]]) getcol(to[i], x);
now[col[x]]--;
}
void delcol(int x, int fa) {
if (!now[col[x]]) {
cnt[col[x]] -= size[x];
Sum -= size[x];
}
now[col[x]]++;
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa && !vis[to[i]]) delcol(to[i], x);
now[col[x]]--;
}
void Count(int x, int fa, int num, long long tot) {
if (!now[col[x]])
num++, tot += cnt[col[x]]; //如果此颜色首次出现,辣么记录 tot 贡献
now[col[x]]++;
ans[x] += Sum - tot + num * Y; // ans的处理
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa && !vis[to[i]]) Count(to[i], x, num, tot);
now[col[x]]--;
}
void work(int x) {
getsize(x, 0); //先把 size 处理出来
Sum = 0, top = 0;
getcol(x, 0); //统计所有子树的 cnt 数组
for (int i = head[x]; i; i = nx[i])
if (!vis[to[i]]) {
now[col[x]]++, delcol(to[i], x), cnt[col[x]] -= size[to[i]],
Sum -= size[to[i]]; //先减去当前子树的贡献,各种减就对了
Y = size[x] - size[to[i]],
Count(to[i], x, 0, 0); // Y 就是其他子树的节点数
getcol(to[i], x), now[col[x]]--, cnt[col[x]] += size[to[i]],
Sum += size[to[i]]; //再把贡献加回来QwQ
}
ans[x] += Sum - cnt[col[x]] + size[x];
for (int i = 1; i <= top; i++) //将出现过的颜色的贡献统统删掉
Book[cbook[i]] = false, cnt[cbook[i]] = 0;
}
void solve(int x) {
vis[x] = true;
work(x); //开始计算贡献
for (int i = head[x]; i; i = nx[i])
if (!vis[to[i]]) {
rt = 0, sum = size[to[i]];
getrt(to[i], x);
solve(rt);
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) scanf("%d", &col[i]);
int u, v;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
add(u, v, i);
add(v, u, i + n);
}
maxs[rt = 0] = sum = n;
getrt(1, 0); //求重心
solve(rt);
for (int i = 1; i <= n; i++) printf("%lld\n", ans[i]);
}

【Luogu P2664】树上游戏的更多相关文章

  1. Luogu P2664 树上游戏 dfs+树上统计

    题目: P2664 树上游戏 分析: 本来是练习点分治的时候看到了这道题.无意中发现题解中有一种方法可以O(N)解决这道题,就去膜拜了一下. 这个方法是,假如对于某一种颜色,将所有这种颜色的点全部删去 ...

  2. [LuoGu]P2664 树上游戏

    Portal 这题真的好. 看到树上路径, 脑子里就要点分治 这一题对于每个点都要计算一遍, 如果暴算实在不好算, 这样我们就可以考虑算贡献. 直接计算每种颜色的贡献. 因为一条过重心的路径中, 可能 ...

  3. luogu P2664 树上游戏(点分治)

    点分治真是一个好东西.可惜我不会 这种要求所有路经的题很可能是点分治. 然后我就不会了.. 既然要用点分治,就想,点分治有哪些优点?它可以\(O(nlogn)\)遍历分治树的所有子树. 那么现在的问题 ...

  4. P2664 树上游戏

    P2664 树上游戏 https://www.luogu.org/problemnew/show/P2664 分析: 点分治. 首先关于答案的统计转化成计算每个颜色的贡献. 1.计算从根出发的路径的答 ...

  5. 洛谷 P2664 树上游戏 解题报告

    P2664 树上游戏 题目描述 \(\text{lrb}\)有一棵树,树的每个节点有个颜色.给一个长度为\(n\)的颜色序列,定义\(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量.以及 ...

  6. ●洛谷P2664 树上游戏

    题链: https://www.luogu.org/problemnew/show/P2664题解: 扫描线,线段树维护区间覆盖 https://www.luogu.org/blog/ZJ75211/ ...

  7. 洛谷P2664 树上游戏

    https://www.luogu.org/problemnew/show/P2664 #include<cstdio> #include<algorithm> #includ ...

  8. 洛谷P2664 树上游戏(点分治)

    传送门 题解 因为一个sb错误调了一个晚上……鬼晓得我为什么$solve(rt)$会写成$solve(v)$啊!!!一个$O(logn)$被我硬生生写成$O(n)$了竟然还能过$5$个点……话说还一直 ...

  9. 洛谷P2664 树上游戏(点分治)

    题意 题目链接 Sol 神仙题..Orz yyb 考虑点分治,那么每次我们只需要统计以当前点为\(LCA\)的点对之间的贡献以及\(LCA\)到所有点的贡献. 一个很神仙的思路是,对于任意两个点对的路 ...

  10. luogu P2644 树上游戏

    一道点分难题 首先很自然的想法就是每种颜色的贡献可以分开计算,然后如果你会虚树就可以直接做了 点分也差不多,考虑每个分治重心的子树对它的贡献以及它对它子树的贡献 首先,处理一个\(cnt\)数组,\( ...

随机推荐

  1. selenium+python爬虫环境搭建

    前言: 准备使用selenium爬取网站数据,先搭建selenium+python爬虫环境搭建 系统环境: 64位win10系统,同时装python2.7和python3.6两个版本,IDE为pych ...

  2. Python学习之旅(二十七)

    Python基础知识(26):常用内建模块(Ⅱ) 1.hashlib Python的hashlib提供了常见的摘要算法,如MD5,SHA1等 摘要算法又称哈希算法.散列算法. (1)它通过一个函数,把 ...

  3. C#实现WinForm禁止最大化、最小化、双击标题栏、双击图标等操作的方法

    from:http://www.jb51.net/article/71319.htm 本文实例讲述了C#实现WinForm禁止最大化.最小化.双击标题栏.双击图标等操作的方法.分享给大家供大家参考.具 ...

  4. Python----Windous下安装python

    一.  python 安装 1. 下载安装包 https://www.python.org/ftp/python/2.7.14/python-2.7.14.amd64.msi # 2.7安装包 htt ...

  5. JS------获取一个时间区间的所有天

    1:获取一个时间区间的所有日期 function getDiffDate(start, end) { var startTime = getDate(start); var endTime = get ...

  6. Gitlab之版本回滚

    gitlab提交错误需要回滚版本 首先查看log找到需要回滚的head git log 回滚 git reset --hard 297ff2dcf20605297684f296a4b4ccaa1cf4 ...

  7. SVN和Git对比梳理

    在日常运维工作中,经常会用到版本控制系统,目前用到最广泛的版本控制器就是SVN和Git,那么这两者之间有什么不同之处呢?SVN(Subversion)是集中式管理的版本控制器,而Git是分布式管理的版 ...

  8. Hadoop开发环境配置1-maven安装配置

    1.下载maven安装包:apache-maven-3.3.9-bin.zip 下载地址: http://archive.apache.org/dist/maven/maven-3/3.3.9/bin ...

  9. javascript call apply

    call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向.因为 JavaScript 的函数存在「定义时上下文」和「 ...

  10. el表达式(一)

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...