点分治
第一场 \(csp\) 后的第一篇博客,我已经 \(OIer\) 大半年了,再也不是之前那个轻狂的小朋友了。。。
所以考虑维护一种算法支持查询树上第 \(k\) 短的路径是否存在。
首先构造两个函数 \(\{dfsdist,dfssize\}\) , 当然其中 \(dfssize\) 主要是计算重心的。
这两个函数相对比较好些,其中 \(dfssize\) 函数需要一个辅助数组 \(max\),表示以 \(u\) 为根切割的最大的子树大小。还要注意作一个 \(sum\),表示递归的树整棵树的大小,然后直接用 \(rt\) 表示子树重心就好。
\(dist[i]\) 表示 \(i \rightarrow u\) 的距离。
这两个函数后面会辅助点分治的作用。
复杂度:
-
\(dfssize\) 的需要递归一遍整颗子树,复杂度也就是整颗子树的大小,可以简单地记作 \(\Theta(n)\) ,但是她并不是整个点分治复杂度瓶颈的一部分,所以这边并不需要在一这个函数的具体复杂度。
-
\(dfsdist\) 是同理的。
代码相对来说比较简单,如下。
void dfs1(int u,int f){
sz[u]=1;
max[u]=0;
for(pii pr:G[u])
if(!vis[v] && v^f){
dfs1(v,u);
sz[u]+=sz[v];
max[u]=std::max(max[u],sz[v]);
}
max[u]=std::max(max[u],sum-sz[u]);
if(max[u]<max[rt]) rt=u;
}
int dd[N],dist[N],tot;
void dfs2(int u,int f){
dd[++tot] = dist[u];
for(pii pr:G[u])
if(v^f && !vis[v])
dist[v]=dist[u]+w,dfs2(v,u);
return;
}
当然,其中的 \(dfs1\) 是计算重心的,\(max[N]\) 数组是上文中的辅助数组。这里需要注意一定要计算一个 \(sum\) 的迭代,否则叶子节点一定会是最优解点,但是显然地,叶子节点不会是重心。
因为最开始的树是一颗无根树,非常不美丽,相当的不好处理。
于是我们钦定一个根 \(rt\) ,那么树上的所有路径可以分成两类:
-
经过 \(rt\) 的路径
-
未经过 \(rt\) 的路径
那么经过 \(rt\) 的路径又可以分为两类:
-
以 \(rt\) 为根的路径
-
不以 \(rt\) 为根的路径
那么我们可以把上述的三种路径描述具象一点:
那么考虑:
钦定节点 \(1\) 为整棵树的根节点,那么路径
\(1\rightarrow 6\) 是以 \(rt\) 为根的路径
\(5\rightarrow 7\) 是未经过 \(rt\) 的路径
\(2\rightarrow 3\) 经过 \(rt\) 的路径
那么我们考虑来维护一下这三种路径。
-
首先是路径 \(1\) ,通过 \(dist[i]\) 数组可以直接计算出 \(K\) 长度的路径是否存在。
-
其次是路径 \(2\) ,通过计算 \(rt\) 为根的树的子树也是可以求解的。
-
最后是路径 \(3\) ,可以考虑对于路径 \(1\) 进行链的合并,比如说路径 \(2\rightarrow 3\) 可拆分为 \(1\rightarrow 3 + 2\rightarrow 1\)
然后直接以 \(dfsdist\) 得到的数据进行处理即可, \(dfssize\) 可以选出树的重心,然后钦定树的重心为根,再进行操作就行了。
#include <bits/stdc++.h>
#define FOR(i,s,n) for(register int i=s;i<=n;++i)
#define ROF(i,n,s) for(register int i=n;i>=s;--i)
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r)>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
#define self rt,l,r
#define pii std::pair<int,int>
#define fir first
#define sec second
#define v (pr.fir)
#define w (pr.sec)
#define gc getchar()
#define re read()
#define add push_back
const int N=1e7+10;
const int maxn=1e7+5;
std::bitset<maxn> vis;
std::vector<pii> G[N];
std::stack<int> stack;
const int inf=1e9;
int sz[N],dfn[N],max[N];
int tf[N];
int rt,sum;
int Q[N],ans[N];
int n,m;
void dfs1(int u,int f){
sz[u]=1;
max[u]=0;
for(pii pr:G[u])
if(!vis[v] && v^f){
dfs1(v,u);
sz[u]+=sz[v];
max[u]=std::max(max[u],sz[v]);
}
max[u]=std::max(max[u],sum-sz[u]);
if(max[u]<max[rt]) rt=u;
}
int dd[N],dist[N],tot;
void dfs2(int u,int f){
dd[++tot] = dist[u];
for(pii pr:G[u])
if(v^f && !vis[v])
dist[v]=dist[u]+w,dfs2(v,u);
return;
}
void dfs3(int u,int f){
tf[0]=true;
stack.push(0);
vis[u]=true;
for(pii pr:G[u]){
if(v^f && !vis[v]){
dist[v]=w;
dfs2(v,u);
for(register int k=1;k<=tot;++k){
for(register int i=1;i<=m;++i){
if(Q[i]>=dd[k]) ans[i] |= tf[Q[i]-dd[k]];
}
}
for(register int k=1;k<=tot;++k)
if(dd[k] < maxn) stack.push(dd[k]) , tf[dd[k]]=true;
tot=0;
}
}
while(!stack.empty())
tf[stack.top()]=false,stack.pop();
for(pii pr:G[u])
if(!vis[v] && v^f){
sum=sz[v];
rt=0;
max[rt]=inf;
dfs1(v,u);
dfs1(rt,-1);
dfs3(rt,u);
}
}
int read(void){
int res=0;
char ch=0;
while(!isdigit(ch)) ch=gc;
while(isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=gc;
return res;
}
int main(){
n=re,m=re;
for(register int i=1;i<n;++i)
{
int a,b,c;
a=re,b=re,c=re;
G[a].add({b,c});
G[b].add({a,c});
}
for(register int i=1;i<=m;++i)
Q[i] = re;
sum=n;
rt=0;
max[rt]=inf;
dfs1(1,-1);
dfs1(rt,-1);
dfs3(rt,-1);
for(register int i=1;i<=m;++i)
if(ans[i])
puts("AYE");
else
puts("NAY");
exit(0);
}
复杂度分析:
递归层数最高 \(\log_2^2 n \times n \thickapprox n \log_2^2 n\)
菊花图会被卡爆,找错重心会被卡爆,爆炸了。
但是好像有优化办法,笔者正在寻找。
现在这里放一句 \(cnblog\) 上面对复杂度的分析
若我们 每次选择子树的重心作为根节点,可以保证递归层数最少,时间复杂度为 O(n\log n)。