树上游戏

题目描述

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. C++多态性----运算符重载与虚函数

    一.多态性 ①概述:多态是指同样的消息被不同类型的对象接收时导致的不同行为. ②类型: 可以分为四类:重载多态.强制多态.包含多态.参数多态. ------------------------ --- ...

  2. 我的Vue朝圣之路2

    1.创建第一个Vue案例 1. 引入Vue.js   2. 创建Vue对象      el : 指定根element(选择器)      data : 初始化数据(页面可以访问)  3. 双向数据绑定 ...

  3. Android自动化测试之Monkey 转自:LupuX

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u011436666/article/details/53998332 在之前的文章Android自动 ...

  4. Luogu4707 重返现世 min-max容斥、DP

    传送门 kthMinMax的唯一模板? 首先你需要知道kth Min-Max定理的内容:\(kthmax(S) = \sum\limits_{T \subseteq S} (-1)^{|T| - k} ...

  5. 6:Partial Update 内部原理 和 乐观锁并发控制

    Partial Update 内部执行过程: 首先,ES文档是不可变的,它们只能被修改,不能被替换.Update Api 也不例外. Update API 简单使用与之前描述相同的 检索-修改-重建索 ...

  6. Java字节流文件复制

    1.字节流 在 Java 中,文件的复制使用字节输入流和字节输出流实现,java.io 包有 InputStream 和 OutputStream 这两个顶层抽象类规范了读写文件所需的核心 API. ...

  7. [C#]DataTable转string[]

    来源:https://zhidao.baidu.com/question/1754089856824824548.html string[] ary = Array.ConvertAll<Dat ...

  8. 笔谈OpenGL ES(三)

    昨天下午以及今天一天,公司安排了新员工培训课程,占用了自己的一些时间,但是也了解到一些新的有利于自身的东西.进公司就要进有完善公司体系和制度的公司,小公司真的是没搞头的,我体验过,反正小公司以后是不会 ...

  9. 利用.bat脚本使得可运行jar开机自动运行

    1.利用Elipse到处可运行的jar包 2.写.bat脚本[点此下载],相应目录自己根据需要修改即可 3.将此脚本放在"启动"文件夹中

  10. Apache Commons FileUpload实现文件上传

    一.Apache Commons-FileUpload简介 Apache Commons是一个专注于可重用Java组件的所有方面的 Apache 项目. Apache Commons项目由三个部分组成 ...