【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\)数组,\( ...
随机推荐
- js各种获取当前窗口页面宽度、高度的方法
alert($(window).height()); //浏览器时下窗口可视区域高度 alert($(document).height()); //浏览器时下窗口文档的高度 alert($(docum ...
- python全栈开发 * 14 知识点汇总 * 180530
14 生成器表达式 内置函数# 一.迭代器 (补充)# 1.如何判断迭代对象,迭代器# (1).dir(obj)检测对象有没有iter方法,然后it=obj.__iter__() 获取迭代器 , it ...
- 下载频道--IT资源关东煮第二期[申明:来源于网络]
下载频道–IT资源关东煮第二期[申明:来源于网络] 地址:http://geek.csdn.net/news/detail/129509?ref=myread
- UCloud首尔机房整体热迁移是这样炼成的
小结: 1.把两个机房在逻辑上变成一个机房: 2.新老机房的后端服务使用同一套 ZooKeeper,但是配置的却是不同的 IP: 3.UCloud内部服务所使用的数据库服务为MySQL, 内部MySQ ...
- RoR - Advanced Querying
Seeding the Database: db/seed.rb 可以提供预设data rake db:seed #seeds.rb Person.create! [ {first_name : &q ...
- ARGB与RGB、RGBA的区别
ARGB 是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构. RGB 色彩模式是工业界的一种颜色标准,是通过对红(R).绿(G).蓝(B)三个颜色通道的变 ...
- Date Calendar
1 毫秒值概念 时间和日期的计算,必须依赖毫秒值获取当前日期的毫秒值:System.currentTimeMillis() 返回值long类型参数, 时间原点:公元1970年1月1日,午夜0:00:0 ...
- python之SQLAlchemy组件
介绍 SQLAlchemy 是一个 ORM 框架,可以帮助我们使用面向对象的方式快速实现数据库操作. 组成部分: Engine,框架的引擎 Connection Pooling ,数据库连接池 Dia ...
- c# Resolve SQlite Concurrency Exception Problem (Using Read-Write Lock)
This article describes the c# example to solve the problem of SQlite concurrent exception method. To ...
- python使用grpc调用rpc接口
proto文件: syntax = "proto3"; package coupon; // //message UnsetUseC2URequest { // int64 bid ...