2023.4.21【图论】点分治
2023.4.21【图论】点分治
(点分治其实是广泛地统计树上全局路径问题的算法,此处采用luogu模板题的题面)
题目描述
给定一棵有
- 对于
的数据,保证 , , , , 。
算法描述
点分治,又名淀粉质,是计算树上路径问题的算法,与树剖不同的是,它计算的是对于整棵树的所有路径的情况,树剖难以完成。此题有
我们发现,对于过x,但是同时经过x的父亲的路径,在
这是计算(calc)函数:
inline void calc(int x)
{
top = 0;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(vis[to]) continue;
dis[to] = e[i].w;
rem[0] = 0;//rem[0]是计数器,rem是队列
getdis(to,x);
for(int j = 1;j <= rem[0];j++)
for(int k = 1;k <= m;k++)
if(query[k] - rem[j] >= 0)
if(judge[query[k] - rem[j]] == 1)
rt[k] = 1;
for(int j = 1;j <= rem[0];j++)
if(rem[j] <= T)
q[++top] = rem[j],judge[rem[j]] = 1;
}
for(int i = 1;i <= top;i++) judge[q[i]] = 0;
}
时间复杂度
讲解视频:https://www.bilibili.com/video/BV1GJ411x7h7
上条链不就被卡死了qwq
这里需要打破传统的树形问题的遍历顺序,而是每次处理一棵子树时,以这棵子树的重心为根进行处理,这样就能保证把子树切开后,大小一定会小于原来的
找重心部分:(一个点为根子树大小的最大值最小)
inline void dfs(int x,int last)
{
siz[x] = 1;
int now = 0;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || vis[to]) continue;
dfs(to,x);
siz[x] += siz[to];
now = max(now,siz[to]);
}
now = max(now,sum - siz[x]);
if(now < Minsiz) Minsiz = now,root = x;
}
注意到一个细节:函数里面的“整棵树大小”用了一个
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5,T = 1e7 + 5;
struct Edge{
int v,w,next;
}e[N * 5];
int head[N],n,m,vis[N],dis[N],query[N],judge[N * 100],rt[N],siz[N],rem[N],root = 0,Minsiz = 0x3f3f3f3f,tot = 0,sum,q[N],top = 0;
inline void add(int x,int y,int z)
{
++tot;
e[tot].v = y;
e[tot].w = z;
e[tot].next = head[x];
head[x] = tot;
}
inline void dfs(int x,int last)
{
siz[x] = 1;
int now = 0;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || vis[to]) continue;
dfs(to,x);
siz[x] += siz[to];
now = max(now,siz[to]);
}
now = max(now,sum - siz[x]);
if(now < Minsiz) Minsiz = now,root = x;
}
inline void getdis(int x,int last)
{
rem[++rem[0]] = dis[x];
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || vis[to]) continue;
dis[to] = dis[x] + e[i].w;
getdis(to,x);
}
}
inline void calc(int x)
{
top = 0;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(vis[to]) continue;
dis[to] = e[i].w;
rem[0] = 0;
getdis(to,x);
for(int j = 1;j <= rem[0];j++)
for(int k = 1;k <= m;k++)
if(query[k] - rem[j] >= 0)
if(judge[query[k] - rem[j]] == 1)
rt[k] = 1;
for(int j = 1;j <= rem[0];j++)
if(rem[j] <= T)
q[++top] = rem[j],judge[rem[j]] = 1;
}
for(int i = 1;i <= top;i++) judge[q[i]] = 0;
}
inline void solve(int x)
{
judge[0] = 1;
vis[x] = 1;
calc(x);
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(vis[to]) continue;
sum = siz[to];
Minsiz = 0x3f3f3f3f;
dfs(to,0);
solve(root);
}
}
int main()
{
memset(siz,0,sizeof(siz));
int x,y,z;
cin>>n>>m;
for(int i = 1;i <= n - 1;i++)
{
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
for(int i = 1;i <= m;i++) cin>>query[i];
sum = n;
dfs(1,0);
memset(judge,0,sizeof(judge));
memset(rt,0,sizeof(rt));
memset(vis,0,sizeof(vis));
solve(root);
for(int i = 1;i <= m;i++)
if(rt[i])
cout<<"AYE"<<endl;
else
cout<<"NAY"<<endl;
return 0;
}
我们关注到一个细节,就是这道题目的询问是满足
点分树闪亮登场
题目描述plus
有一颗节点个数为
0 x k
查询与
1 x y
将点
强制在线,
算法描述plus
点分树,是将点分治的过程离线下来变成一棵树的过程,考虑一般的暴力:统计子树信息,修改后一层一层跳父亲,会有
有以下结论:点分树满足两点的LCA必在原树这两点的路径上。
显然地,两个点的路径长等于它们分别到LCA路径长的和,我们就可以对于每个点,用一个动态开点线段树开桶,维护子树中节点到这个点的距离为
然后就WA了。
显然地,统计祖先节点的贡献时会把原来来自
总时间复杂度
tips:对于大多数数据范围为
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5,inf = 0x3f3f3f3f;
struct Edge{
int v,next;
}e[N * 2];
int head[N],val[N],n,m,tot = 0,vis[N],siz[N],sum,Minsiz = inf,root,dfa[N],dep[N],fa[N],rt1[N],rt2[N],a[N * 50],lc[N * 50],rc[N * 50],dis[N],top[N],son[N];
inline void pushup(int pos)
{
a[pos] = a[lc[pos]] + a[rc[pos]];
}
inline void modify(int l,int r,int x,int k,int pos)
{
if(l == r)
{
a[pos] += k;
return;
}
int mid = (l + r) >> 1;
if(x <= mid)
{
if(!lc[pos]) lc[pos] = ++tot;
modify(l,mid,x,k,lc[pos]);
}
else
{
if(!rc[pos]) rc[pos] = ++tot;
modify(mid + 1,r,x,k,rc[pos]);
}
pushup(pos);
}
inline int query(int l,int r,int L,int R,int pos)
{
if(pos == 0) return 0;
if(L <= l && r <= R)
return a[pos];
int ret = 0,mid = (l + r) >> 1;
if(L <= mid)
ret += query(l,mid,L,R,lc[pos]);
if(R > mid)
ret += query(mid + 1,r,L,R,rc[pos]);
return ret;
}
inline void add(int x,int y)
{
++tot;
e[tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
inline void dfs0(int x,int last)
{
fa[x] = last;
dep[x] = dep[last] + 1;
siz[x] = 1;
son[x] = 0;
for(register int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last) continue;
dfs0(to,x);
siz[x] += siz[to];
if(siz[to] > siz[son[x]]) son[x] = to;
}
}
inline void dfs1(int x,int last)
{
if(son[x])
{
top[son[x]] = top[x];
dfs1(son[x],x);
}
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || to == son[x]) continue;
top[to] = to;
dfs1(to,x);
}
}
inline int getlca(int x,int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] > dep[top[y]]) x = fa[top[x]];
else y = fa[top[y]];
}
return (dep[x] < dep[y]) ? x : y;
}
inline void dfs(int x,int last)
{
siz[x] = 1;
int now = 0;
for(register int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || vis[to]) continue;
dfs(to,x);
siz[x] += siz[to];
now = max(now,siz[to]);
}
now = max(now,sum - siz[x]);
if(now < Minsiz) Minsiz = now,root = x;
}
inline int getd(int x,int y)
{
return dep[x] + dep[y] - 2 * dep[getlca(x,y)];
}
inline void solve(int x)
{
vis[x] = 1;
rt1[x] = ++tot;
rt2[x] = ++tot;
for(register int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(vis[to]) continue;
Minsiz = inf;
sum = siz[to];
dfs(to,0);
dfa[root] = x;
solve(root);
}
}
inline int q(int x,int k)
{
int ret = 0,pre = 0,cur = x;
while(cur)
{
if(getd(cur,x) > k)
{
pre = cur;
cur = dfa[cur];
continue;
}
ret += query(0,n - 1,0,k - getd(cur,x),rt1[cur]);
if(pre)
ret -= query(0,n - 1,0,k - getd(cur,x),rt2[pre]);
pre = cur;cur = dfa[cur];
}
return ret;
}
inline void md(int x,int k)
{
int cur = x;
while(cur)
{
modify(0,n - 1,getd(cur,x),k - val[x],rt1[cur]);
if(dfa[cur])
modify(0,n - 1,getd(dfa[cur],x),k - val[x],rt2[cur]);
cur = dfa[cur];
}
}
inline int read()
{
int s = 0,w = 1;
char k = getchar();
while(k > '9' || k < '0')
{
if(k == '-') w = -w;
k = getchar();
}
while(k <= '9' && k >= '0')
{
s = s * 10 + k - '0';
k = getchar();
}
return s * w;
}
inline void write(int x)
{
if(!x) return;
write(x / 10);
putchar((x % 10) + '0');
}
int main()
{
memset(fa,0,sizeof(fa));
n = read();m = read();
for(register int i = 1;i <= n;i++) val[i] = read();
int x,y,z;
for(register int i = 1;i <= n - 1;i++)
{
x = read();y = read();
add(x,y);
add(y,x);
}
dep[0] = 0;
dfs0(1,0);
fa[1] = 1;
top[1] = 1;
dfs1(1,0);
memset(siz,0,sizeof(siz));
tot = 0;
sum = n;
dfs(1,0);
solve(root);
for(register int i = 1;i <= n;i++) md(i,val[i] << 1);
int lastans = 0;
for(register int i = 1;i <= m;i++)
{
x = read();y = read();z = read();
y ^= lastans;z ^= lastans;
if(x == 0)
{
lastans = q(y,z);
if(lastans == 0)
putchar('0');
else
write(lastans);
putchar('\n');
}
else
md(y,z),val[y] = z;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话