点分治

点分治,每次先找到当前联通块的重心,然后以重心为当前联通块的根节点,枚举并遍历根节点的一个子树,子树中的每个节点到根节点产生一条路径,如果这条路径的长度为 $x$,则在数据结构中查询此前被遍历过的子树中是否存在长度为 $k-x$ 的路径。

遍历完成后将当前子树的信息加入数据结构,这里用一个数组维护就行。不能边遍历子树边修改数据结构,这样会导致同一子树内节点的累加出不存在的路径长度。记录数组中哪些位置被使用,清空时只情空这些位置,否则时间复杂度是错误的。

由于以重心为根时,重心的最大孩子节点大小不超过整棵树节点数量的一半,执行一轮分治后树上连通块的大小至少减少到原先的二分之一,在 $O(\log n)$ 轮后连通块大小归零。由于一个点至多被遍历到 $O(\log n)$ 次,时间复杂度 $O(n \log n)$。

共有 $m$ 次询问,总体时间复杂度 $O(nm \log n)$。

#include<bits/stdc++.h>
using namespace std;
struct Star{int w,to;Star(int x,int y):w(x),to(y){}};
constexpr int lim=10'000'005;
int n,m,q[105],w[10'005],wson[10'005];
bool ans[105],on[10'005];
vector<int> sta,res,path;
vector<Star> v[10'005];
bitset<10'000'005> s;
void fisdfs(int x,int las){
    w[x]=1,wson[x]=0,sta.push_back(x);
    for(auto i:v[x]) if(i.to!=las&&!on[i.to]){
        fisdfs(i.to,x),w[x]+=w[i.to];
        if(w[i.to]>w[wson[x]]) wson[x]=i.to;
    }
}
void dfs(int x,int las,int dep){
    res.push_back(dep),path.push_back(dep);
    for(int i=1;i<=m;i++) if(dep<=q[i]&&s[q[i]-dep]) ans[i]=1;
    for(auto i:v[x]) if(i.to!=las&&!on[i.to]) dfs(i.to,x,dep+i.w);
}
void dfz(int x){
    sta.clear(),res.clear(),fisdfs(x,0);
    int ro=0;
    for(int i:sta) if(max(w[wson[i]],w[x]-w[i])*2<=w[x]) ro=i;
    on[ro]=1;
    for(auto i:v[ro]) if(!on[i.to]){
        path.clear(),dfs(i.to,0,i.w);
        for(int j:path) if(j<=lim) s[j]=1;
    }
    for(int i:res) if(i<=lim) s[i]=0;
    for(auto i:v[ro]) if(!on[i.to]) dfz(i.to);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n-1;i++){
        static int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        v[x].push_back(Star(w,y));
        v[y].push_back(Star(w,x));
    }
    for(int i=1;i<=m;i++) scanf("%d",&q[i]);
    s[0]=1,dfz(1);
    for(int i=1;i<=m;i++){
        if(ans[i]) cout<<"AYE"<<'\n';
        else cout<<"NAY"<<'\n';
    }
    return 0;
}
最后修改:2024 年 10 月 25 日
如果觉得我的文章对你有用,请随意赞赏