洛谷【P2664】树上游戏
浅谈树分治: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】树上游戏的更多相关文章
- 洛谷 P2664 树上游戏 解题报告
P2664 树上游戏 题目描述 \(\text{lrb}\)有一棵树,树的每个节点有个颜色.给一个长度为\(n\)的颜色序列,定义\(s(i,j)\) 为 \(i\) 到 \(j\) 的颜色数量.以及 ...
- ●洛谷P2664 树上游戏
题链: https://www.luogu.org/problemnew/show/P2664题解: 扫描线,线段树维护区间覆盖 https://www.luogu.org/blog/ZJ75211/ ...
- 洛谷P2664 树上游戏(点分治)
传送门 题解 因为一个sb错误调了一个晚上……鬼晓得我为什么$solve(rt)$会写成$solve(v)$啊!!!一个$O(logn)$被我硬生生写成$O(n)$了竟然还能过$5$个点……话说还一直 ...
- 洛谷P2664 树上游戏
https://www.luogu.org/problemnew/show/P2664 #include<cstdio> #include<algorithm> #includ ...
- 洛谷P2664 树上游戏(点分治)
题意 题目链接 Sol 神仙题..Orz yyb 考虑点分治,那么每次我们只需要统计以当前点为\(LCA\)的点对之间的贡献以及\(LCA\)到所有点的贡献. 一个很神仙的思路是,对于任意两个点对的路 ...
- 【刷题】洛谷 P2664 树上游戏
题目描述 lrb有一棵树,树的每个节点有个颜色.给一个长度为n的颜色序列,定义s(i,j) 为i 到j 的颜色数量.以及 \[sum_i=\sum_{j=1}^ns(i,j)\] 现在他想让你求出所有 ...
- 洛谷P2664 树上游戏 【点分治 + 差分】
题目 lrb有一棵树,树的每个节点有个颜色.给一个长度为n的颜色序列,定义s(i,j) 为i 到j 的颜色数量.以及 现在他想让你求出所有的sum[i] 输入格式 第一行为一个整数n,表示树节点的数量 ...
- 洛谷P2664 树上游戏——点分治
原题链接 被点分治虐的心态爆炸了 题解 发现直接统计路径上的颜色数量很难,考虑转化一下统计方式.对于某一种颜色\(c\),它对一个点的贡献为从这个点出发且包含这种颜色的路径条数. 于是我们先点分一下, ...
- [洛谷U40581]树上统计treecnt
[洛谷U40581]树上统计treecnt 题目大意: 给定一棵\(n(n\le10^5)\)个点的树. 定义\(Tree[l,r]\)表示为了使得\(l\sim r\)号点两两连通,最少需要选择的边 ...
- P2664 树上游戏
P2664 树上游戏 https://www.luogu.org/problemnew/show/P2664 分析: 点分治. 首先关于答案的统计转化成计算每个颜色的贡献. 1.计算从根出发的路径的答 ...
随机推荐
- Android - 单例模式(singleton)的使用
单例模式(singleton)的使用 本文地址:http://blog.csdn.net/caroline_wendy 单例(singleton)是特殊的Java类,在创建实例时.一个类仅同意创建一个 ...
- JS 的引用赋值与传值赋值
这个问题说大不大说小不小,如果你有幸踩了这个坑,一定会找这篇文章,哈哈~ 现说一下JS数字的类型:基本类型和引用类型 先看下下面两个栗子: var a = 30; var b = a; a = 20; ...
- Chrome自带恐龙小游戏的源码研究(六)
在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来 ...
- checkStyle使用具体解释
简单介绍 checkStyle是一款代码格式检查工具.它依据设置好的编码规则来自己主动检查代码.比方命名规范,文件长度.代码行长度等等.代码检查工具是保证项目代码质量.统一编码风格的一种重要途径.本篇 ...
- js时间戳格式化成日期格式的多种方法
js需要把时间戳转为为普通格式,一般的情况下可能用不到的, 下面先来看第一种吧 复制代码代码如下: function getLocalTime(nS) { return new Date(parseI ...
- python opener代理
链接:http://www.jb51.net/article/46495.htm https://www.cnblogs.com/cunyusup/p/7341829.html
- Circling Round Treasures CodeForces - 375C
C. Circling Round Treasures time limit per test 1 second memory limit per test 256 megabytes input s ...
- EasyNVR RTSP摄像机HLS直播服务器中使用Onvif协议获取设备快照
我们知道EasyNVR中可以获取快照信息,之前的文章也说明了EasyNVR是如何进行快照抓取的 这里我们使用另一种方法进行快照的抓取 流程 获取设备能力Capabilities 获取设备的能力,并且可 ...
- python书写日志的重要性?
转自:https://blog.csdn.net/weixin_43063753/article/details/82899395 程序为什么要写日志?#为了能够在程序在运行过程中记录错误,方便维护, ...
- github commit, issue, pull request, project
1 github的提供给用户操作和交流的几个对象 commit, issue, pull request and project 2 commit and commit comment commit就 ...