浅谈树分治:https://www.cnblogs.com/AKMer/p/10014803.html

题目传送门:https://www.luogu.org/problemnew/show/P2664

对于所有求颜色种类数的问题,我们都可以定义一个方向,使得所有的颜色在最靠这个方向第一次出现的位置有效,而其它位置都是无效的。对于树分治,我们可以定义这个方向为当前需要遍历的子树,反方向就是已经遍历完的子树。

对于一个点\(u\),如果从当前重心到他这一条路径上,该点颜色是第一次出现,那么它的颜色将给后面的遍历带来\(siz[u]\)的贡献。另外,在遍历当前子树时,所有在重心到当前点这条路径的上的颜色,贡献都是已经遍历过的子树的总结点数。正过来做一遍,反过来做一遍就可以了。对于单独的从重心到当前点的路径会被统计两次,所以要减掉一次。

边分治重构树之后不知道怎么消除新结点的影响,如果有大佬愿意教教我请在评论下方回复。

这题数据貌似比较水,不卡不重构树的边分治。

时间复杂度:\(O(nlogn)\)

空间复杂度:\(O(n)\)

点分治版代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll; const int maxn=1e5+5; bool vis[maxn];
ll ans[maxn],res;
int n,tot,mx,rt,N,Siz;
int now[maxn],pre[maxn<<1],son[maxn<<1];
int col[maxn],siz[maxn],cnt[maxn],V[maxn],sum[maxn]; int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
} void add(int a,int b) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b;
} struct rubbish {
bool bo[maxn];
int sta[maxn],top; void clear() {
Siz=res=0;
while(top) {
bo[sta[top]]=0;
cnt[sta[top--]]=0;
}
} void ins(int id) {
if(bo[id])return;
bo[id]=1,sta[++top]=id;
}
}R; void find_rt(int fa,int u) {
int res=0;siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)find_rt(u,v),siz[u]+=siz[v],res=max(res,siz[v]);
res=max(res,N-siz[u]);
if(res<mx)mx=res,rt=u;
} void dfs(int fa,int u) {
sum[col[u]]++,res+=(sum[col[u]]==1);
ans[u]-=res,siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)dfs(u,v),siz[u]+=siz[v];
sum[col[u]]--,res-=(sum[col[u]]==0);
} void query(int fa,int u) {
sum[col[u]]++;if(sum[col[u]]==1)res-=cnt[col[u]],res+=Siz+1;
ans[u]+=res;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)query(u,v);
sum[col[u]]--;if(sum[col[u]]==0)res+=cnt[col[u]],res-=Siz+1;
} void solve(int fa,int u) {
sum[col[u]]++;
if(sum[col[u]]==1) {
cnt[col[u]]+=siz[u];
res+=siz[u];R.ins(col[u]);
}
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)solve(u,v);
sum[col[u]]--;
} void work(int u,int size) {
N=size,mx=rt=n+1,find_rt(0,u);
u=rt,vis[u]=1,tot=0;
sum[col[u]]++;res++;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v])V[++tot]=v,dfs(u,v);
sum[col[u]]--;res--;
for(int i=1;i<=tot;i++) {
int v=V[i];
sum[col[u]]++,res-=cnt[col[u]],res+=Siz+1;
query(u,v);
sum[col[u]]--,res+=cnt[col[u]],res-=Siz+1;
solve(u,v),Siz+=siz[v];
}R.clear();
for(int i=tot;i;i--) {
int v=V[i];
sum[col[u]]++,res-=cnt[col[u]],res+=Siz+1;
query(u,v);
sum[col[u]]--,res+=cnt[col[u]],res-=Siz+1;
solve(u,v),Siz+=siz[v];
}ans[u]+=res+Siz+1-cnt[col[u]];R.clear();
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v])work(v,siz[v]);
} int main() {
n=read();
for(int i=1;i<=n;i++)
col[i]=read();
for(int i=1;i<n;i++) {
int a=read(),b=read();
add(a,b),add(b,a);
}work(1,n);
for(int i=1;i<=n;i++)
printf("%lld\n",ans[i]);
return 0;
}

不重构树的边分治版代码如下:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll; const int maxn=2e5+5; bool vis[maxn];
ll ans[maxn],res;
int m,n,tot=1,mx,id,N;
int now[maxn],pre[maxn<<1],son[maxn<<1];
int col[maxn],siz[maxn],cnt[maxn],sum[maxn]; vector<int>to[maxn];
vector<int>::iterator it; int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
} void add(int a,int b) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b;
} struct Rubbish {
bool bo[maxn];
int sta[maxn],top; void clear() {
res=0;
while(top)cnt[sta[top]]=bo[sta[top]]=0,top--;
} void ins(int id) {
if(bo[id])return;
bo[id]=1,sta[++top]=id;
}
}R; void find_edge(int fa,int u) {
siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa) {
find_edge(u,v),siz[u]+=siz[v];
if(abs(N-2*siz[v])<mx)
mx=abs(N-2*siz[v]),id=p>>1;
}
} void dfs(int fa,int u) {
siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)dfs(u,v),siz[u]+=siz[v];
} void solve(int fa,int u) {
sum[col[u]]++;
if(sum[col[u]]==1) {
cnt[col[u]]+=siz[u];
res+=siz[u],R.ins(col[u]);
}
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)solve(u,v);
sum[col[u]]--;
} void query(int fa,int u,int num) {
sum[col[u]]++;
if(sum[col[u]]==1)res+=num,res-=cnt[col[u]];
ans[u]+=res;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)query(u,v,num);
sum[col[u]]--;
if(sum[col[u]]==0)res-=num,res+=cnt[col[u]];
} void work(int u,int size) {
if(size<2)return;
N=size,mx=id=m+1,find_edge(0,u),vis[id]=1;
int u1=son[id<<1],u2=son[id<<1|1];
dfs(0,u1),dfs(0,u2);
solve(0,u1),query(0,u2,siz[u1]),R.clear();
solve(0,u2),query(0,u1,siz[u2]),R.clear();
work(u1,siz[u1]),work(u2,siz[u2]);
} int main() {
m=n=read();
for(int i=1;i<=n;i++)
col[i]=read();
for(int i=1;i<n;i++) {
int a=read(),b=read();
add(a,b),add(b,a);
}
work(1,m);
for(int i=1;i<=n;i++)printf("%lld\n",ans[i]+1);
return 0;
}

洛谷【P2664】树上游戏的更多相关文章

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

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

  2. ●洛谷P2664 树上游戏

    题链: https://www.luogu.org/problemnew/show/P2664题解: 扫描线,线段树维护区间覆盖 https://www.luogu.org/blog/ZJ75211/ ...

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

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

  4. 洛谷P2664 树上游戏

    https://www.luogu.org/problemnew/show/P2664 #include<cstdio> #include<algorithm> #includ ...

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

    题意 题目链接 Sol 神仙题..Orz yyb 考虑点分治,那么每次我们只需要统计以当前点为\(LCA\)的点对之间的贡献以及\(LCA\)到所有点的贡献. 一个很神仙的思路是,对于任意两个点对的路 ...

  6. 【刷题】洛谷 P2664 树上游戏

    题目描述 lrb有一棵树,树的每个节点有个颜色.给一个长度为n的颜色序列,定义s(i,j) 为i 到j 的颜色数量.以及 \[sum_i=\sum_{j=1}^ns(i,j)\] 现在他想让你求出所有 ...

  7. 洛谷P2664 树上游戏 【点分治 + 差分】

    题目 lrb有一棵树,树的每个节点有个颜色.给一个长度为n的颜色序列,定义s(i,j) 为i 到j 的颜色数量.以及 现在他想让你求出所有的sum[i] 输入格式 第一行为一个整数n,表示树节点的数量 ...

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

    原题链接 被点分治虐的心态爆炸了 题解 发现直接统计路径上的颜色数量很难,考虑转化一下统计方式.对于某一种颜色\(c\),它对一个点的贡献为从这个点出发且包含这种颜色的路径条数. 于是我们先点分一下, ...

  9. [洛谷U40581]树上统计treecnt

    [洛谷U40581]树上统计treecnt 题目大意: 给定一棵\(n(n\le10^5)\)个点的树. 定义\(Tree[l,r]\)表示为了使得\(l\sim r\)号点两两连通,最少需要选择的边 ...

  10. P2664 树上游戏

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

随机推荐

  1. Synchronized修饰静态变量和普通变量的区别

    这里主要涉及到类对象(static方法),对象方法(非static方法) 我们知道,当synchronized修饰一个static方法时,多线程下,获取的是类锁(即Class本身,注意:不是实例): ...

  2. load-on-startup 解释

    <!DOCTYPE web-app PUBLIC  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  &qu ...

  3. Log4j2升级jar包冲突问题

    升级Log4j2后日志提示jar包冲突: SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar ...

  4. python从安装与使用pip到入门

    官方下载地址:https://www.python.org/downloads/ 下载后直接安装就可以了 再配一下环境变量, cmd运行python -V (注意,这里是大写的V) 打开python跑 ...

  5. EasyPlayer windows RTSP播放器OCX插件使用说明

    鉴于大家对于EasyPlayer插件的使用还不太熟悉,特此写一篇插件的使用文档,供大家参考:EasyPlayer插件有两种,一种是基于IE的ActiveX控件,一种是基于FireFox(也支持多浏览器 ...

  6. 高复用率的RTSPClient组件EasyRTSPClient设计流程概述

    EasyRTSPClient 设计过程 概述 EasyRTSPClient 基于live555构建而成. 今天讲讲EasyRTSPClient的设计过程 EasyRTSPClient,主要包括以下部分 ...

  7. framemarker的使用

    1 什么是framemarker framemarker是网页模版和数据模型的结合体.装载网页的时候,framemarker自动从数据模型中提取数据并生成html页面. 2 framemarker怎么 ...

  8. 我的Java开发学习之旅------>求N内所有的素数

    一.素数的概念 质数(prime number)又称素数,有无限个.一个大于1的自然数,除了1和它本身外,不能被其他自然数(质数)整除,换句话说就是该数除了1和它本身以外不再有其他的因数:否则称为合数 ...

  9. 高性能 Socket 组件 HP-Socket v3.2.1-RC1 公布

    HP-Socket 是一套通用的高性能 TCP/UDP Socket 组件.包括服务端组件.client组件和 Agent 组件.广泛适用于各种不同应用场景的 TCP/UDP 通信系统.提供 C/C+ ...

  10. STL容器元素应满足的条件

    要使用C++中的标准模板库中的容器,其元素要满足以下三个条件: 元素必须可以通过copy构造函数进行复制,且二者进行相等测试返回true. 元素必须可以通过赋值操作符完成赋值操作. 元素必须可以通过析 ...