LG2664 树上游戏
树上游戏
题目描述
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]
输入输出样例
说明
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的颜色,待会儿容斥掉)
这个性质很显然。
那么我们就可以这样做了:
对树进行第一遍dfs,预处理size和上方性质中每个颜色的贡献color,同时记录color总和sum
枚举根的所有儿子子树,先把子树扫一遍清除其在color数组中的所有贡献(排除同一子树内部的错误贡献)。接着,对于该子树中的每一个点j:
设X=sigma color[j 到根上(不包括根)的所有颜色] (由于这些颜色已经出现过,我们不能在该子树外计算其贡献)
设num为j到根上(不包括根)的颜色数
设Y为size[root]-size[该子树(注意不是j)](即所有其他子树+根的点数)
则ans[j]+=sum-X+num*Y别忘了计算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 树上游戏的更多相关文章
- 「LG2664 树上游戏」
题目 这真是一道神仙的一批的题目 定义\(s(i,j)\)表示从点\(i\)到点\(j\)经过的颜色数量 设 \[sum_i=\sum_{j=1}^ns(i,j)\] 求出所有的\(sum_i\) 考 ...
- 【Luogu2664】树上游戏(点分治)
[Luogu2664]树上游戏(点分治) 题面 洛谷 题解 很好的一道点分治题. 首先直接点分治,考虑过每个分治重心的链的贡献. 我们从分治重心开始找每种颜色,强制令一种颜色只在其到分治重心的链上第一 ...
- 洛谷 P2664 树上游戏 解题报告
P2664 树上游戏 题目描述 \(\text{lrb}\)有一棵树,树的每个节点有个颜色.给一个长度为\(n\)的颜色序列,定义\(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量.以及 ...
- P2664 树上游戏
P2664 树上游戏 https://www.luogu.org/problemnew/show/P2664 分析: 点分治. 首先关于答案的统计转化成计算每个颜色的贡献. 1.计算从根出发的路径的答 ...
- Luogu P2664 树上游戏 dfs+树上统计
题目: P2664 树上游戏 分析: 本来是练习点分治的时候看到了这道题.无意中发现题解中有一种方法可以O(N)解决这道题,就去膜拜了一下. 这个方法是,假如对于某一种颜色,将所有这种颜色的点全部删去 ...
- [LuoGu]P2664 树上游戏
Portal 这题真的好. 看到树上路径, 脑子里就要点分治 这一题对于每个点都要计算一遍, 如果暴算实在不好算, 这样我们就可以考虑算贡献. 直接计算每种颜色的贡献. 因为一条过重心的路径中, 可能 ...
- 【Luogu P2664】树上游戏
Problem Description \(lrb\) 有一棵树,树的每个节点有个颜色.给一个长度为 \(n\) 的颜色序列,定义 \(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量.以 ...
- 洛谷P2664 树上游戏(点分治)
传送门 题解 因为一个sb错误调了一个晚上……鬼晓得我为什么$solve(rt)$会写成$solve(v)$啊!!!一个$O(logn)$被我硬生生写成$O(n)$了竟然还能过$5$个点……话说还一直 ...
- luoguP2664树上游戏(点分治)
题目链接:https://www.luogu.org/problem/P2664 题意:给定一颗带点权的树,结点数n<=1e5,点权<=1e5,用s(i,j)表示从i到j的路径上不同点权数 ...
随机推荐
- 微设计基础架构(MDI)
微设计基础架构(MDI) 了解微设计基础架构(MDI)的概念,它们如何帮助开发,以及它们与DevOps和微服务等技术的关系. 技术决策既困难又严肃,可以决定项目的成败.如何找到合适的技术栈?“微设计基 ...
- 各种实用的 PHP 开源库推荐【转】
转自: https://my.oschina.net/editorial-story/blog/882780 PHP 是一种通用开源脚本语言.语法吸收了 C 语言.Java 和 Perl 的特点,利于 ...
- LeetCode 238. 除自身以外数组的乘积(Product of Array Except Self)
238. 除自身以外数组的乘积 238. Product of Array Except Self 题目描述 LeetCode LeetCode238. Product of Array Except ...
- linux svn开机自动启动服务
SVN设置开机自动启动 usr/lib/systemd/system/添加svn.service文件 home/sdbdatasvn/svnrepos(换成绝对路径) 如果出现权限问题,请chmod ...
- centos7安装php7.3
安装php7.3 CentOS/RHEL 7.x: yum install epel-release yum install http://rpms.remirepo.net/enterprise/r ...
- python学习67-面向对象-封装
封装 1.什么是封装? 根据名字寓意为:把一个东西装起来,然后密封,类似这样的面向对象的编程为封装. 真正的封装是明确的区别内外,只能在内部用,外部无法调用. 2. 举例: class Car: _s ...
- 【LEETCODE】44、509. Fibonacci Number
package y2019.Algorithm.array; /** * @ProjectName: cutter-point * @Package: y2019.Algorithm.array * ...
- Navicat 连接mysql 报错: Authentication plugin caching_ sha2_password cannot be loaded
出现这个错误的时候, 网上的资料都是修改mysql的登录信息的, ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password ...
- 【转载】 C#中通过Where方法查找出所有符合条件的元素集合
在C#的List集合对象中,FirstOrDefault方法可以用于查找List集合中符合条件的第一个元素,如果需要根据条件查找到List集合中的所有符合条件的元素对象集合,则需要使用到List集合的 ...
- 5G能带来什么改变-从鸿蒙OS说起
背景 从5G投票事件开始,开始关注5G.许多文章都说到5G的特点有速度快.时延低,其中,时延低是最重要的特点.然而,时延低能给社会带来什么改变呢? 2G是短信的时代,3G促成了语音视频,4G促成了短视 ...