浅谈树分治: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. 《C专家编程》数组和指针并不同--多维数组

    <C专家编程>数组和指针并不同 标签(空格分隔): 程序设计论著笔记 1. 背景理解 1.1 区分定义与声明 p83 声明相当于普通声明:它所说明的并不是自身,而是描写叙述其它地方创建的对 ...

  2. 智能手机的耗电特征及APP耗电量测试的两种方法

    文章陈述了手机发展趋势及耗电特性,集中讨论了时下最为关心的智能手机耗电问题,并介绍了测量手机软件耗电量的两种方法.此外还解释了为何运营商此前会提出收取微信的费用,心跳机制是什么. 美国著名手机公司Pa ...

  3. android 在githup中的资源整理(转)

    1.Github开源Android组件资源整理(一) 个性化控件(View) 2.Github开源Android组件资源整理(二)ActionBar和Menu 3. Github开源Android组件 ...

  4. Grails 简要

    一.什么是Grails? Grails is an Open Source, full stack, web application framework for the JVM. It takes a ...

  5. linux 打印系统时间操作

    版权为个人所有,如需转载请说明出处.(东北大亨) http://www.cnblogs.com/northeastTycoon/p/5511498.html 1. 打开shell脚本 例子1:输出两天 ...

  6. 九度OJ 1055:数组逆置 (基础题)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:7324 解决:3429 题目描述: 输入一个字符串,长度小于等于200,然后将数组逆置输出. 输入: 测试数据有多组,每组输入一个字符串. ...

  7. power law 幂定律

    y=cx∧a 卖品销量排名与销量

  8. hadoop2.3安装过程及问题解决

    三台serveryiprod01,02,03,当中01为namenode,02为secondarynamenode.3个均为datanode 3台server的这里提到的配置均需一样. 0.安装前提条 ...

  9. docker 网络模式研究了许久,其实我们需要的是docker run -p 80:80命令

    我们只是希望能够从外部访问到docker而已,并不需要去折腾该死的网络模式,桥接,host等等. -p: 端口映射,格式为:主机(宿主)端口:容器端口 sudo docker run -t -i  - ...

  10. 剑指Offer:对称的二叉树【28】

    剑指Offer:对称的二叉树[28] 题目描述 请实现一个函数,用来判断一颗二叉树是不是对称的.注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的. 题目分析 Java题解 /* publi ...