【Luogu P2664】树上游戏
Problem
Description
\(lrb\) 有一棵树,树的每个节点有个颜色。给一个长度为 \(n\) 的颜色序列,定义 \(s(i,j)\) 为 \(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】树上游戏的更多相关文章
- Luogu P2664 树上游戏 dfs+树上统计
题目: P2664 树上游戏 分析: 本来是练习点分治的时候看到了这道题.无意中发现题解中有一种方法可以O(N)解决这道题,就去膜拜了一下. 这个方法是,假如对于某一种颜色,将所有这种颜色的点全部删去 ...
- [LuoGu]P2664 树上游戏
Portal 这题真的好. 看到树上路径, 脑子里就要点分治 这一题对于每个点都要计算一遍, 如果暴算实在不好算, 这样我们就可以考虑算贡献. 直接计算每种颜色的贡献. 因为一条过重心的路径中, 可能 ...
- luogu P2664 树上游戏(点分治)
点分治真是一个好东西.可惜我不会 这种要求所有路经的题很可能是点分治. 然后我就不会了.. 既然要用点分治,就想,点分治有哪些优点?它可以\(O(nlogn)\)遍历分治树的所有子树. 那么现在的问题 ...
- P2664 树上游戏
P2664 树上游戏 https://www.luogu.org/problemnew/show/P2664 分析: 点分治. 首先关于答案的统计转化成计算每个颜色的贡献. 1.计算从根出发的路径的答 ...
- 洛谷 P2664 树上游戏 解题报告
P2664 树上游戏 题目描述 \(\text{lrb}\)有一棵树,树的每个节点有个颜色.给一个长度为\(n\)的颜色序列,定义\(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量.以及 ...
- ●洛谷P2664 树上游戏
题链: https://www.luogu.org/problemnew/show/P2664题解: 扫描线,线段树维护区间覆盖 https://www.luogu.org/blog/ZJ75211/ ...
- 洛谷P2664 树上游戏
https://www.luogu.org/problemnew/show/P2664 #include<cstdio> #include<algorithm> #includ ...
- 洛谷P2664 树上游戏(点分治)
传送门 题解 因为一个sb错误调了一个晚上……鬼晓得我为什么$solve(rt)$会写成$solve(v)$啊!!!一个$O(logn)$被我硬生生写成$O(n)$了竟然还能过$5$个点……话说还一直 ...
- 洛谷P2664 树上游戏(点分治)
题意 题目链接 Sol 神仙题..Orz yyb 考虑点分治,那么每次我们只需要统计以当前点为\(LCA\)的点对之间的贡献以及\(LCA\)到所有点的贡献. 一个很神仙的思路是,对于任意两个点对的路 ...
- luogu P2644 树上游戏
一道点分难题 首先很自然的想法就是每种颜色的贡献可以分开计算,然后如果你会虚树就可以直接做了 点分也差不多,考虑每个分治重心的子树对它的贡献以及它对它子树的贡献 首先,处理一个\(cnt\)数组,\( ...
随机推荐
- IDEA 快捷将创建main函数
在编写代码的时候直接输入psv就会看到一个psvm的提示,此时点击tab键一个main方法就写好了. psvm 也就是public static void main的首字母. 依次还有在方法体内键入f ...
- java中的静态代理和动态代理
1.动态代理的定义:为其他对象提供一个代理以控制对这个对象的访问 代理类主要负责委托类的预处理消息,过滤消息,把消息传给委托类以及消息事后处理 按照代理类的创建时期,代理类可以分为2种:静态代理类(在 ...
- yum命令查看某个命令是由那个包提供的
[root@linux-node2 ~]$ yum whatprovides fuserLoaded plugins: fastestmirrorLoading mirror speeds from ...
- JavaScript基础知识(数据类型)
数据类型 布尔:true/fasle console.log(typeof true);// "boolean" Number : true -->1 false --> ...
- cmd运行java程序---路径容易出错的问题
初学者在首次使用cmd运行java程序时面临着很多的问题,重要的基本为“设置环境变量过程”与运行过程中的“路径出错问题”.由于环境变量设置的网络分享更多,且为大众情况,因此比较容易解决! 由于本人 ...
- 算法基础_递归_求杨辉三角第m行第n个数字
问题描述: 算法基础_递归_求杨辉三角第m行第n个数字(m,n都从0开始) 解题源代码(这里打印出的是杨辉三角某一层的所有数字,没用大数,所以有上限,这里只写基本逻辑,要符合题意的话,把循环去掉就好) ...
- linux终端提示符显示bash-4.2#
原因是root在/root下面的几个配置文件丢失,丢失文件如下: 1..bash_profile 2..bashrc 以上这些文件是每个用户都必备的文件. 使用以下命令从主默认文件重新拷贝一份配置信息 ...
- 【托业】【新东方托业全真模拟】TEST07~08-----P5~6
unless ---conj:barring(除非,不包括)perp+名词短语 be capable of doing 有能力做某事 qualified commensurate with 与……相应 ...
- 019-并发编程-java.util.concurrent之-Semaphore 信号量
一.概述 Semaphore是一个计数信号量.从概念上将,Semaphore包含一组许可证.如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证.每个release()方法都会 ...
- spring--给配置文件.properties加密
11111111111编写类并继承PropertyPlaceholderConfigurer.java package com.xx.encryptDecrypt; import java.util. ...