点分治
1.给定一个带边权的树,共有 \(m\) 个询问,询问距离为 \(k\) 的点对是否存在
做法1:暴力dfs
做法2:\(lca\) (时间复杂度\(O(n^2\log n)\))
做法3:点分治 (时间复杂度\(O(n\log n)\))
思路:
1.取一个节点 \(u\)
2.统计经过\(u\)的链
经过\(u\) 的链两端点必定在不同子树中
记录子节点到根节点的距离\(dist[i]\),依次统计所有子树,开一个\(lens\)数组记录已有路径长度。
当遍历一个子树时,查找是否存在\(lens[k-dist[i]]\)即可。
遍历完一个子树,开个栈存储该子树的\(dist\),再把该子树的路径全部加到\(lens\)中。
清空\(lens\)数组,为保证时间复杂度,开个栈记录添加的\(lens\),只清空栈中的元素即可
3.向下递归
如何选取节点:
若该树是一条链,如果每次都找其儿子递归,要递归\(n\)次,复杂度为\(O(n^2)\)
所以每次选取的节点\(u\)应该为该树的重心,只需递归\(\log n\)次,复杂度为\(O(n\log n)\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=20010,K=100000100,INF=0x3f3f3f3f;
int n,q,rt,minn=INF,root,maxx2;
int qs[N],ans[N];
int h[N],e[M],ne[M],w[M],idx;
int s[N],lens[K];
int stk[N],tt,stk2[N],tt2;
bool del[N];//该点是否已被遍历
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x;
}
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int fa,int u,int zs)//找重心
{
int maxx=0;
s[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||del[j])continue;
dfs(u,j,zs);
s[u]+=s[j];
maxx=max(maxx,s[j]);
}
maxx=max(maxx,zs-s[u]);
if(maxx<minn)minn=maxx,rt=u;
}
void dfs2(int fa,int u,int len)//统计以u为根的子树穿过u的路径
{
if(len>maxx2)return;//长度大于询问的最大值,不用再向下统计
stk[++tt]=len;
for(int i=0;i<q;i++){
if(qs[i]-len>=0&&qs[i]-len<K){
if(lens[qs[i]-len]||len==qs[i])ans[i]=1;//存在另一子树长度为qs[i]-len,或其本身长度为qa[i]
}
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(del[j]||j==fa)continue;
dfs2(u,j,len+w[i]);
}
}
void query(int u)
{
del[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(del[j])continue;
dfs2(u,j,w[i]);
for(int l=1;l<=tt;l++){
if(!lens[stk[l]])stk2[++tt2]=stk[l];
lens[stk[l]]++;//将该子树的len加到lens中
}
tt=0;
}
for(int i=1;i<=tt2;i++)lens[stk2[i]]=0;//只清空栈中元素
tt2=0;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(del[j])continue;
minn=INF,rt=-1;
int zs=s[j];
dfs(u,j,zs);
dfs(0,rt,zs);//维护size值(似乎不用)
query(rt);//从每个子树重心开始递归
}
}
int main()
{
memset(h,-1,sizeof h);
n=read(),q=read();
for(int i=1;i<n;i++){
int a,b,c;
a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);
}
dfs(0,1,n);
root=rt;
for(int i=0;i<q;i++)qs[i]=read(),maxx2=max(maxx2,qs[i]);//预处理出询问,统一查找
query(root);
for(int i=0;i<q;i++){
if(ans[i])puts("AYE");
else puts("NAY");
}
return 0;
}
给定一棵 n 个节点的树,每条边有边权,求出树上两点距离小于等于 k的点对数量。
可用线段树维护,也有一种容斥+双指针的方法。
处理当前子树经过其根节点的所有长度,存到a数组中:
void getdis(int fa,int u,int len)
{
dis[u]=len;
a[++cnt]=dis[u];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||del[j])continue;
getdis(u,j,len+w[i]);
}
}
标签:log,int,复杂度,分治,len,lens,节点
From: https://www.cnblogs.com/lzaqwq/p/17760686.html