树上游戏

题目描述

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

$$sum_i=\sum_{j=1}^ns(i,j)$$

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

输入输出格式

输入格式:

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

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

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

输出格式:

输出n行,第i行为sum[i]

输入输出样例

输入样例#1:
复制

5
1 2 3 2 3
1 2
2 3
2 4
1 5
输出样例#1:
复制

10
9
11
9
12

说明

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

对于40%的数据,n<=2000

对于100%的数据,1<=n,c[i]<=10^5

题解

这个统计还是有点意思,说下它的两种解法。

Treeloveswater的点分治

.

往点分治方向思考,问题就变成了:你有一棵树,如何\(O(n)\)的处理出,以根为lca的点对的答案?

一个很重要的性质:

对于树中的一点i,如果该点的颜色在该点到根这条链上是第一次出现,那么对于这棵树的其他与i的lca为根点j(即在不同子树内),均能与i的子树(包括i)组成点对,i的颜色会对j的答案贡献size[i]。(我们在此暂且不考虑j到根的链上是否出现了i的颜色,待会儿容斥掉)

这个性质很显然。

那么我们就可以这样做了:

  1. 对树进行第一遍dfs,预处理size和上方性质中每个颜色的贡献color,同时记录color总和sum

  2. 枚举根的所有儿子子树,先把子树扫一遍清除其在color数组中的所有贡献(排除同一子树内部的错误贡献)。接着,对于该子树中的每一个点j:

    设X=sigma color[j 到根上(不包括根)的所有颜色] (由于这些颜色已经出现过,我们不能在该子树外计算其贡献)

    设num为j到根上(不包括根)的颜色数

    设Y为size[root]-size[该子树(注意不是j)](即所有其他子树+根的点数)

    则ans[j]+=sum-X+num*Y

  3. 别忘了计算root的ans

    ans[root]+=sum-color[根的颜色]+size[root]

那么点分治就解决了这个问题,时间复杂度\(O(n\log n)\)。统计方法值得学习。

看一下别人的代码。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define o 200011
#define ll long long
using namespace std;
const int inf=1e8;
int head[o],nxt[o*2],point[o*2],V[o];
ll color[o],ans[o],much,sum,num,size[o],cnt[o],total,record;
int tot,n,ui,vi,root;
bool vis[o*2];
void addedge(int x,int y){
tot++;nxt[tot]=head[x];head[x]=tot;point[tot]=y;
tot++;nxt[tot]=head[y];head[y]=tot;point[tot]=x;
}
void findroot(int now,int dad){
size[now]=1;
ll maxson=0;
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(v!=dad&&!vis[tmp]){
findroot(v,now);
size[now]+=size[v];
maxson=max(maxson,size[v]);
}
}
maxson=max(maxson,total-size[now]);
if(maxson<record) root=now,record=maxson;
}
void dfs1(int now,int dad){
size[now]=1;
cnt[V[now]]++;
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad){
dfs1(v,now);
size[now]+=size[v];
}
}
if(cnt[V[now]]==1){
sum+=size[now];
color[V[now]]+=size[now];
}
cnt[V[now]]--;
}
void change(int now,int dad,int value){
cnt[V[now]]++;
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad) change(v,now,value);
}
if(cnt[V[now]]==1){
sum+=(ll)size[now]*value;
color[V[now]]+=(ll)size[now]*value;
}
cnt[V[now]]--;
}
void dfs2(int now,int dad){
cnt[V[now]]++;
if(cnt[V[now]]==1){
sum-=color[V[now]];
num++;
}
ans[now]+=sum+num*much;
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad) dfs2(v,now);
}
if(cnt[V[now]]==1){
sum+=color[V[now]];
num--;
}
cnt[V[now]]--;
}
void clear(int now,int dad){
cnt[V[now]]=0;
color[V[now]]=0;
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad) clear(v,now);
}
}
void solve(int now,int dad){
dfs1(now,dad);
ans[now]+=sum-color[V[now]]+size[now];
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad){
cnt[V[now]]++;
sum-=size[v];
color[V[now]]-=size[v];
change(v,now,-1);
cnt[V[now]]--;
much=size[now]-size[v];
dfs2(v,now);
cnt[V[now]]++;
sum+=size[v];
color[V[now]]+=size[v];
change(v,now,1);
cnt[V[now]]--;
}
}
sum=0;num=0;
clear(now,dad);
for(int tmp=head[now];tmp;tmp=nxt[tmp]){
int v=point[tmp];
if(!vis[tmp]&&v!=dad){
vis[tmp]=true;
vis[tmp^1]=true;
total=size[v];
record=inf;
findroot(v,now);
solve(root,0);
}
}
}
int main(){
tot=1;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&V[i]);
for(int i=1;i<n;i++){
scanf("%d%d",&ui,&vi);
addedge(ui,vi);
}
record=inf;
total=n;
findroot(1,0);
solve(root,0);
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}

sxd666888的树上差分

分开计算每种颜色对答案的贡献。

我们考虑把树中这种颜色的点都删掉,那么就会有很多的小树,这些小树中的点互相之间不会产生贡献,而不同树的两个点之间会产生贡献。我们可以得到点的sum要+=n - 所在小树的size。

因此,一个点的sum=n * 颜色数 - 计算每种颜色节点时该点所在小树的size。发现我们只需要计算减号后的部分。

考虑在每棵小树的树根(深度最小)计算这棵小树的size,这样既方便计算也方便向下传递。我们用surp[i]记录把fa对应颜色删掉后i所在小树(i一定是这棵小树的树根)的size。

如何算所有颜色对一个点的贡献总和呢?直接维护总和sum,考虑在i时继承总和sum,把sum加上surp[i],减去上一次同一颜色的surp更新,就行了。

特殊处理一下整棵树的根节点就好了。时间复杂度\(O(n)\)。

看一下此人的毒瘤命名代码。

#include<bits/stdc++.h>
using namespace std;
long long read()
{
char ch=getchar();long long x=0,ff=1;
while(ch<'0'||ch>'9') {if(ch=='-') ff=-1;ch=getchar();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*ff;
}
void write(long long aa)
{
if(aa<0) putchar('-'),aa=-aa;
if(aa>9) write(aa/10);
putchar('0'+aa%10);
return;
}
long long n,sum,qwq;
long long vis[100005],ans[100005];
long long tot,head[100005],nx[200005],to[200005];
long long col[100005],sz[100005],jian[100005];
long long lz[100005],bj[100005];
void jia(long long aa,long long bb)
{
tot++;
nx[tot]=head[aa];
to[tot]=bb;
head[aa]=tot;
return;
}
void dfs(long long rt,long long fa)
{
sz[rt]=1;
long long tmp=jian[col[fa]];//遍历时删的个数
for(long long i=head[rt];i;i=nx[i])
{
long long yy=to[i];
if(yy==fa) continue;
dfs(yy,rt);
sz[rt]+=sz[yy];
}
jian[col[rt]]++;//删点
if(fa)
{
lz[rt]=sz[rt]-jian[col[fa]]+tmp;//子树的size - (当前删的个数 - 遍历时删的个数)
jian[col[fa]]+=lz[rt];//删点
}
}
void getans(long long rt,long long fa)
{
long long yuanbj=bj[col[fa]];
qwq+=lz[rt]-bj[col[fa]];//差分啦
bj[col[fa]]=lz[rt];
ans[rt]=n*sum-qwq+bj[col[rt]];//自己颜色的显然是不能删掉的
for(long long i=head[rt];i;i=nx[i])
{
long long yy=to[i];
if(yy==fa) continue;
getans(yy,rt);
}
bj[col[fa]]=yuanbj;
qwq-=lz[rt]-bj[col[fa]];//还原啦
return;
}
int main()
{
n=read();
for(long long i=1;i<=n;++i)
{
col[i]=read();//col[i]<=100000,可能大于n。。。。
if(!vis[col[i]]) vis[col[i]]=1,sum++;//sum颜色种类
}
for(long long i=1;i<n;++i)
{
long long x=read(),y=read();
jia(x,y);jia(y,x);
}
dfs(1,0);
for(long long i=1;i<=100000;++i) if(vis[i]) qwq+=n-jian[i],bj[i]=n-jian[i];//特别处理根节点
getans(1,0);
for(long long i=1;i<=n;++i) write(ans[i]),puts("");
return 0;
}

LG2664 树上游戏的更多相关文章

  1. 「LG2664 树上游戏」

    题目 这真是一道神仙的一批的题目 定义\(s(i,j)\)表示从点\(i\)到点\(j\)经过的颜色数量 设 \[sum_i=\sum_{j=1}^ns(i,j)\] 求出所有的\(sum_i\) 考 ...

  2. 【Luogu2664】树上游戏(点分治)

    [Luogu2664]树上游戏(点分治) 题面 洛谷 题解 很好的一道点分治题. 首先直接点分治,考虑过每个分治重心的链的贡献. 我们从分治重心开始找每种颜色,强制令一种颜色只在其到分治重心的链上第一 ...

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

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

  4. P2664 树上游戏

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

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

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

  6. [LuoGu]P2664 树上游戏

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

  7. 【Luogu P2664】树上游戏

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

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

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

  9. luoguP2664树上游戏(点分治)

    题目链接:https://www.luogu.org/problem/P2664 题意:给定一颗带点权的树,结点数n<=1e5,点权<=1e5,用s(i,j)表示从i到j的路径上不同点权数 ...

随机推荐

  1. 微设计基础架构(MDI)

    微设计基础架构(MDI) 了解微设计基础架构(MDI)的概念,它们如何帮助开发,以及它们与DevOps和微服务等技术的关系. 技术决策既困难又严肃,可以决定项目的成败.如何找到合适的技术栈?“微设计基 ...

  2. 各种实用的 PHP 开源库推荐【转】

    转自: https://my.oschina.net/editorial-story/blog/882780 PHP 是一种通用开源脚本语言.语法吸收了 C 语言.Java 和 Perl 的特点,利于 ...

  3. LeetCode 238. 除自身以外数组的乘积(Product of Array Except Self)

    238. 除自身以外数组的乘积 238. Product of Array Except Self 题目描述 LeetCode LeetCode238. Product of Array Except ...

  4. linux svn开机自动启动服务

    SVN设置开机自动启动 usr/lib/systemd/system/添加svn.service文件 home/sdbdatasvn/svnrepos(换成绝对路径) 如果出现权限问题,请chmod  ...

  5. centos7安装php7.3

    安装php7.3 CentOS/RHEL 7.x: yum install epel-release yum install http://rpms.remirepo.net/enterprise/r ...

  6. python学习67-面向对象-封装

    封装 1.什么是封装? 根据名字寓意为:把一个东西装起来,然后密封,类似这样的面向对象的编程为封装. 真正的封装是明确的区别内外,只能在内部用,外部无法调用. 2. 举例: class Car: _s ...

  7. 【LEETCODE】44、509. Fibonacci Number

    package y2019.Algorithm.array; /** * @ProjectName: cutter-point * @Package: y2019.Algorithm.array * ...

  8. Navicat 连接mysql 报错: Authentication plugin caching_ sha2_password cannot be loaded

    出现这个错误的时候, 网上的资料都是修改mysql的登录信息的, ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password ...

  9. 【转载】 C#中通过Where方法查找出所有符合条件的元素集合

    在C#的List集合对象中,FirstOrDefault方法可以用于查找List集合中符合条件的第一个元素,如果需要根据条件查找到List集合中的所有符合条件的元素对象集合,则需要使用到List集合的 ...

  10. 5G能带来什么改变-从鸿蒙OS说起

    背景 从5G投票事件开始,开始关注5G.许多文章都说到5G的特点有速度快.时延低,其中,时延低是最重要的特点.然而,时延低能给社会带来什么改变呢? 2G是短信的时代,3G促成了语音视频,4G促成了短视 ...