树上差分的两种形式(相遇 or 行程的交集 题解)
emm,这很NOIP...
写这个的原因是今天考试一个sb差分树题居然杠了个树剖上去,杀鸡用了牛刀。
而且不止一次了...总是想不到子树和这种差分,淦
Anyway,简单写写吧。
树上差分的两种形式
-
单点修改&链查询用差分转化为单点修改&树上前缀和
class MPQU{public: //单点修改 & 树上前缀和 (Modify Point Query Up) BIT bit; MPQU(bool typ=0){if(typ==1) bit=BIT(dN);} void Add(ll u,ll w){bit.Add(st[u],w); bit.Add(ed[u],-w);} ll Query(ll u){return bit.Query(st[u]);} }; class MPQL{public: //单点修改 & 链查询 (Modify Point Query Link) MPQU mpqu; MPQL(bool typ=0){if(typ==1) mpqu=MPQU(1);} void Add(ll u,ll w){mpqu.Add(u,w);} ll Query(ll u,ll v){ll lca=LCA(u,v); return mpqu.Query(u)+mpqu.Query(v)-mpqu.Query(lca)-mpqu.Query(fa[lca]);} }mpql; -
链修改&单点查询用差分转化为单点修改&子树和
class MPQD{public: //单点修改 & 子树和 (Modify Point Query Down) BIT bit; MPQD(bool typ=0){if(typ==1) bit=BIT(dN);} void Add(ll u,ll w){bit.Add(st[u],w);} ll Query(ll u){return bit.Query(ed[u])-bit.Query(st[u]-1);} }; class MLQP{public: //链修改 & 单点查询 (Modify Link Query Point) MPQD mpqd; MLQP(bool typ=0){if(typ==1) mpqd=MPQD(1);} void Add(ll u,ll v){ll lca=LCA(u,v); mpqd.Add(u,1); mpqd.Add(v,1); mpqd.Add(lca,-1); mpqd.Add(fa[lca],-1);} ll Query(ll u){return mpqd.Query(u);} }mlqp;
三重封装
转化后的问题都能用DFS序+树状数组解决。(在上文代码中,树状数组用BIT
封装,st, ed
是DFS序)
本质:
-
树上前缀和对应序列前缀和
-
子树和对应序列后缀和
在序列上,前缀和、后缀和均能解决这两个问题。
而之所以在树上这两种形式求解的问题出现不同,是因为与序列相比,树是上小下大的。
相遇 or 行程的交集 题解
这道题综合的考察了两种差分,成功把我干翻(
Description
豪哥生活在一个n个点的树形城市里面,每一天都要走来走去。虽然走的是比较的多,但是豪哥在这个城市里面的朋友并不是很多。
当某一天,猴哥给他展现了一下大佬风范之后,豪哥决定要获得一些交往机会来提升交往能力。豪哥现在已经物色上了一条友,打算和它(豪哥并不让吃瓜群众知道性别)交往。豪哥现在spy了一下这个人的所有行程起点和终点,豪哥打算从终点开始走到起点与其相遇。但是豪哥是想找话题的,他想知道以前有多少次行程和此次行程是有交集的,这样豪哥就可以搭上话了。这个路径与之前路径的有交集数量作为豪哥此次的交往机会。
但是豪哥急着要做交往准备,所以算什么交往机会的小事情就交给你了。Input
第一行一个正整数n表示节点个数。
接下来n-1行,每行两个正整数分别是u,v表示节点u和v之间有连边。
接下来一行一个 正整数m表示路径个数。
然后有m行,每行两个正整数分别是u,v分别表示u到v之间有一条路径。Output
输出共m行,每行一个整数,第i行表示豪哥在这条路径上获得的交往机会。
Sample Input
5 1 2 1 3 3 4 3 5 4 4 5 4 2 1 3 1 2 Sample Output
0 1 2 2 Hint
【数据范围与约定】
对于20%的数据n,m≤2000
对于另外20%的数据n,m≤50000
对于另外10%的数据n,m≤200000,保证树形结构是一条链
对于另外50%的数据n,m≤200000
简要题意:给你一棵树,然后按顺序输入若干条路径。问每次输入路径时该路径与前面已经输入的多少条路径有交集。
主要要想到把两条树上路径有交集转换为其中某条路径的LCA被另一条路径所覆盖。
于是对于当前输入的路径,分当前路径覆盖了多少个之前路径的LCA和当前路径的LCA被之前多少个路径覆盖讨论即可。前者是单点修改&链查询,后者是链修改&单点查询,按前文的方法做就可以了。
注意特判LCA重叠的情况,见代码。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<ctime> #include<cstdlib> #include<queue> #include<algorithm> #include<map> #include<set> #include<vector> using namespace std; typedef long long ll; typedef vector<ll> Vector; typedef vector<ll>::iterator VecIt; ll Rd(){ ll ans=0;bool fh=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-') fh=1; c=getchar();} while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar(); if(fh) ans=-ans; return ans; } ll Lowbit(ll x){return x&(-x);} const ll PTN=2E5+5; class BIT{public: vector<ll> tr;ll n; BIT(){} BIT(ll n){this->n = n; tr=vector<ll>(n+1,0);} void Add(ll p,ll w){ if(!p) return; for(ll i=p;i<=n;i+=Lowbit(i)) tr[i]+=w; } ll Query(ll p){ ll ans=0; for(ll i=p;i>=1;i-=Lowbit(i)) ans+=tr[i]; return ans; } }; ll N; Vector edge[PTN]; ll fa[PTN],dep[PTN],sz[PTN],son[PTN]; ll st[PTN],ed[PTN];ll dN; void DFS1(ll u,ll father,ll depth){ fa[u]=father;dep[u]=depth;sz[u]=1;son[u]=0;st[u]=++dN; for(VecIt it = edge[u].begin(); it!=edge[u].end(); it++){ ll v=(*it);if(v==father) continue; DFS1(v,u,depth+1); sz[u]+=sz[v]; if(sz[v]>sz[son[u]]) son[u]=v; } ed[u]=++dN; } ll tp[PTN]; void DFS2(ll u,ll toop){ tp[u]=toop; if(son[u]) DFS2(son[u],toop); for(VecIt it = edge[u].begin(); it!=edge[u].end(); it++){ ll v=(*it);if(v==fa[u]) continue; if(v!=son[u]) DFS2(v,v); } } ll LCA(ll u,ll v){ while(tp[u]!=tp[v]){ if(dep[tp[u]]<dep[tp[v]]) swap(u,v); u=fa[tp[u]]; } if(dep[u]<dep[v]) return u; else return v; } class MPQU{public: //单点修改 & 树上前缀和 (Modify Point Query Up) BIT bit; MPQU(bool typ=0){if(typ==1) bit=BIT(dN);} void Add(ll u,ll w){bit.Add(st[u],w); bit.Add(ed[u],-w);} ll Query(ll u){return bit.Query(st[u]);} }; class MPQD{public: //单点修改 & 子树和 (Modify Point Query Down) BIT bit; MPQD(bool typ=0){if(typ==1) bit=BIT(dN);} void Add(ll u,ll w){bit.Add(st[u],w);} ll Query(ll u){return bit.Query(ed[u])-bit.Query(st[u]-1);} }; class MPQL{public: //单点修改 & 链查询 (Modify Point Query Link) MPQU mpqu; MPQL(bool typ=0){if(typ==1) mpqu=MPQU(1);} void Add(ll u,ll w){mpqu.Add(u,w);} ll Query(ll u,ll v){ll lca=LCA(u,v); return mpqu.Query(u)+mpqu.Query(v)-mpqu.Query(lca)-mpqu.Query(fa[lca]);} }mpql; class MLQP{public: //链修改 & 单点查询 (Modify Link Query Point) MPQD mpqd; MLQP(bool typ=0){if(typ==1) mpqd=MPQD(1);} void Add(ll u,ll v){ll lca=LCA(u,v); mpqd.Add(u,1); mpqd.Add(v,1); mpqd.Add(lca,-1); mpqd.Add(lca,-1);/*mpqd.Add(fa[lca],-1);*/} //特殊处理重叠LCA ll Query(ll u){return mpqd.Query(u);} }mlqp; int main(){ //freopen("meet.in","r",stdin); //freopen("meet.out","w",stdout); N=Rd(); for(ll i=1;i<N;i++){ ll u=Rd(),v=Rd(); edge[u].push_back(v); edge[v].push_back(u); } dN=0;sz[0]=0;DFS1(1,0,0); DFS2(1,1); st[0]=0;ed[0]=0; mpql=MPQL(1);mlqp=MLQP(1); ll qN=Rd(); for(ll q=1;q<=qN;q++){ ll u=Rd(),v=Rd(); ll lca=LCA(u,v); printf("%lld\n",mpql.Query(u,v)+mlqp.Query(lca)); mpql.Add(lca,1); mlqp.Add(u,v); } return 0; }
2020/10/24
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现