点分治

点分治

以树的重心为根,对子树内的问题分治求解,时间复杂度可以做到 \(O(n\log n\times F(n))\),其中 \(F(n)\)解决经过根的问题所需要的处理

P3806 模板

给一棵有边权的树,多次询问树上是否存在距离为 \(k\) 的点对。

\(n\le 10^4,m\le 100,k\le 10^7\)

假设现在 \(rt\) 是根,则问题转化为求是否存在不在 \(rt\) 同一子树内的 \(u,v\) 使得 \(dis(rt,u)+dis(rt,v)=k\)

转化一下式子得 \(dis(rt,u)=k-dis(rt,v)\),我们记 \(is[x]\) 表示是否出现过 \(x\),即对于每一个 \(u\),如果 \(is[k-dis(rt,v)]=1\),则说明有解;对于不在同一子树的限制,直接对于每个子树处理完再加入 \(is\) 就能避免。

时间复杂度 \(O(mn\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+3;
const int inf=0x3f3f3f3f;
int n,m;
struct edge{
    int v,w;
    edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
bool isk[10000003];
bool vis[maxn];
bool ans[maxn];
int maxk,sum;
void add(int u,int v,int w){
    e[u].emplace_back(edge(v,w));
    e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
    siz[u]=1; mxsiz[u]=0;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            centroid(v.v,u);
            siz[u]+=siz[v.v];
            mxsiz[u]=max(mxsiz[u],siz[v.v]);
        }
    }
    mxsiz[u]=max(mxsiz[u],sum-siz[u]);
    if(!rt||mxsiz[u]<mxsiz[rt]){
        rt=u;
    }
}
void distance(int u,int fa){
    d.emplace_back(dis[u]);
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            dis[v.v]=dis[u]+v.w;
            distance(v.v,u);
        }
    }
}
void calc(int u){
    for(edge v:e[u]){
        if(!vis[v.v]){
            dis[v.v]=v.w;
            distance(v.v,u);
            for(int i:d){
                for(int j=1;j<=m;j++){
                    if(quest[j]>=i){
                        ans[j]|=isk[quest[j]-i];
                    }
                }
            }
            while(!d.empty()){
                int i=d.back();
                if(i<=maxk){
                    q.push(i);
                    isk[i]=1;
                }
                d.pop_back();
            }
        }
    }    
    while(!q.empty()){
        isk[q.front()]=0;
        q.pop();
    }
}
void dfz(int u,int fa){
    isk[0]=1;
    vis[u]=1;
    calc(u);
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            sum=siz[v.v];
            rt=0;
            mxsiz[rt]=inf;
            centroid(v.v,0);
            dfz(rt,0);
        }
    }
}

signed main(){
    cin>>n>>m;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        add(u,v,w);
    }
    for(int i=1;i<=m;i++){
        cin>>quest[i];
        maxk=max(maxk,quest[i]);
    }
    sum=n;
    rt=0;
    mxsiz[rt]=inf;
    centroid(1,0);
    dfz(rt,0);
    for(int i=1;i<=m;i++){
        if(ans[i]){
            cout<<"AYE\n";
        }else{
            cout<<"NAY\n";
        }
    }
    return 0; 
}

P4178 Tree

给一棵有边权的树,求树上距离 \(\le k\) 的点对数。

\(n\le 4\times 10^4,k\le 2\times 10^4\)

做法一:

思路同板子,同理转化一下式子得 \(dis(rt,u)\le k-dis(rt,v)\) 这个偏序问题非常经典,直接树状数组维护即可。

时间复杂度 \(O(n\log n\log k)\)

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4+3;
const int inf=0x3f3f3f3f;
int n,m;
struct edge{
    int v,w;
    edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
int tree[maxn];
bool vis[maxn];
int ans[maxn];
int maxk,sum;
void add(int u,int v,int w){
    e[u].emplace_back(edge(v,w));
    e[v].emplace_back(edge(u,w));
}
int lowbit(int x){return x&-x;}
void modify(int x,int c){
    x++;
    for(;x<=maxk+1;x+=lowbit(x)) tree[x]+=c;
}
int query(int x){
    x++;
    int ret=0;
    for(;x;x-=lowbit(x)) ret+=tree[x];
    return ret; 
}
void centroid(int u,int fa){
    siz[u]=1; mxsiz[u]=0;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            centroid(v.v,u);
            siz[u]+=siz[v.v];
            mxsiz[u]=max(mxsiz[u],siz[v.v]);
        }
    }
    mxsiz[u]=max(mxsiz[u],sum-siz[u]);
    if(!rt||mxsiz[u]<mxsiz[rt]){
        rt=u;
    }
}
void distance(int u,int fa){
    d.emplace_back(dis[u]);
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            dis[v.v]=dis[u]+v.w;
            distance(v.v,u);
        }
    }

}

void dfz(int u,int fa){
    modify(0,1);
    q.push(0);
    vis[u]=1;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            dis[v.v]=v.w;
            distance(v.v,u);
            for(int i:d){
                for(int j=1;j<=m;j++){
                    if(quest[j]>=i){
                        ans[j]+=query(quest[j]-i);
                    }
                }
            }
            while(!d.empty()){
                int i=d.back();
                if(i<=maxk){
                    q.push(i);
                    modify(i,1);
                }
                d.pop_back();
            }
        }
    }
    while(!q.empty()){
        modify(q.front(),-1);
        q.pop();
    }
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            sum=siz[v.v];
            rt=0;
            mxsiz[rt]=inf;
            centroid(v.v,u);
            centroid(rt,0);
            dfz(rt,0);
        }
    }
}

signed main(){
    cin>>n;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        add(u,v,w);
    }
    m=1;
    for(int i=1;i<=m;i++){
        cin>>quest[i];
        maxk=max(maxk,quest[i]);
    }
    sum=n;
    rt=0;
    mxsiz[rt]=inf;
    centroid(1,0);
    centroid(rt,0);
    dfz(rt,0);
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<'\n';
    }
    return 0; 
}

做法二:

将所有 \(dis(rt,u)\) 处理出来然后排序,双指针扫一遍 \(dis(rt,L)+dis(rt,R)\le k\) 的,每次更新 \(L\) 的时候答案加上 \(R-L\)

注意此时并不保证 \(L,R\) 不在同一子树,所有需要容斥,加上 \(rt\) 的总答案,再减掉 \(rt\) 子树的答案即可不重不漏。

时间复杂度 \(O(n\log n)\)。某种情况下比做法一更优。

CF293E Close Vertices

给一棵有边权的树,求树上距离 \(\le k\),边数 \(\le l\) 的点对数。

\(l\le n\le 10^5,k\le 10^9\)

考虑延续上题的做法二思路,先双指针扫 \(dis(rt,L)+dis(rt,R)\le k\) 的答案 \(R-L\),但是这些答案并不都满足边数限制。但是可以发现,边数限制其实也是偏序问题,\(len(rt,u)+len(rt,v)\le l\),把 \(len(rt,v)\) 移到右边去,就用树状数组维护即可,具体就先把 \(len(rt,u)\) 全部加到树状数组上,然后根据 \(L,R\) 的变动,不断减去出界的 \(len\)

时间复杂度 \(O(n\log^2 n)\)。同样需要容斥。

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+3;
const int inf=0x3f3f3f3f3f3f3f3f;
#define int long long
int n,m;
struct edge{
    int v,w;
    edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
struct node{
    int dis,dep;
    node(int dis=0,int dep=0): dis(dis),dep(dep){}
    bool operator<(const node &o)const{return dis<o.dis;}
}dis[maxn];
int K,L;
vector<node>d;
int tree[maxn];
bool vis[maxn];
int ans;
int sum;
void add(int u,int v,int w){
    e[u].emplace_back(edge(v,w));
    e[v].emplace_back(edge(u,w));
}
int lowbit(int x){return x&-x;}
void modify(int x,int c){
    if(x<0) return;
    x++;
    for(;x<=L+1;x+=lowbit(x)) tree[x]+=c;
}
int query(int x){
    if(x<0) return 0;
    x++;
    int ret=0;
    for(;x;x-=lowbit(x)) ret+=tree[x];
    return ret; 
}
void centroid(int u,int fa){
    siz[u]=1; mxsiz[u]=0;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            centroid(v.v,u);
            siz[u]+=siz[v.v];
            mxsiz[u]=max(mxsiz[u],siz[v.v]);
        }
    }
    mxsiz[u]=max(mxsiz[u],sum-siz[u]);
    if(!rt||mxsiz[u]<mxsiz[rt]){
        rt=u;
    }
}
void distance(int u,int fa){
    d.emplace_back(dis[u]);
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            dis[v.v].dis=dis[u].dis+v.w;
            dis[v.v].dep=dis[u].dep+1;
            distance(v.v,u);
        }
    }

}
int calc(int u,int fa,int Dis,int Dep){
    dis[u].dis=Dis;
    dis[u].dep=Dep;
    distance(u,fa);
    sort(d.begin(),d.end());
    int l=0,r=d.size()-1,ret=0;
    for(node i:d) modify(i.dep,1);
    while(l<r){
        if(d[l].dis+d[r].dis<=K){
            modify(d[l].dep,-1);
            ret+=query(L-d[l].dep);
            l++;
        }else{
            modify(d[r].dep,-1);
            r--;
        }
    }
    modify(d[l].dep,-1);
    d.clear();
    return ret;
}
void dfz(int u,int fa){
    vis[u]=1;
    ans+=calc(u,0,0,0);
    for(edge v:e[u]){
        if(!vis[v.v]){
            ans-=calc(v.v,u,v.w,1);
            sum=siz[v.v];
            rt=0;
            mxsiz[rt]=inf;
            centroid(v.v,u);
            dfz(rt,0);
        }
    }
}

signed main(){
    cin>>n;
    cin>>L>>K;
    for(int i=1,v,w;i<n;i++){
        cin>>v>>w;
        add(v,i+1,w);
    }
    sum=n;
    rt=0;
    mxsiz[rt]=inf;

    centroid(1,0);
    // centroid(rt,0);
    dfz(rt,0);
    cout<<ans;
    return 0; 
}

其他做法

沿用上题做法一,再套一维数据结构维护,时间复杂度 \(O(n\log^2 n\log k)\) 不太可过。

直接上主席树或 KD-Tree(?)时间复杂度 \(O(n\log^2 n/n\sqrt{n})\)

P2634 聪聪可可

给一棵有边权的树,求树上距离能被 \(3\) 整除的点对数。

\(n\le 2\times 10^4\)

同样沿用模板的大体思路,就是求 \(3\mid dis(rt,u)+dis(rt,v)\) 的方案数。

在计算距离时取模,将余数为 \(0,1,2\) 的数量 \(c\) 存起来,然后统计答案就是 \((0,0),(1,2),(2,1)\) 分组,答案为 \(c_0^2+2c_1c_2\)

注意这样统计答案无法避免 \(u,v\)\(rt\) 同一子树的情况,所以使用容斥,加上 \(rt\) 的总答案,再减掉 \(rt\) 子树的答案即可不重不漏。

\(\sum F(n)=n\),所以时间复杂度 \(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+3;
const int inf=0x3f3f3f3f;
#define int long long
int n,m;
struct edge{
    int v,w;
    edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
int isk[3];
bool vis[maxn];
int ans;
int maxk,sum;
void add(int u,int v,int w){
    e[u].emplace_back(edge(v,w));
    e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
    siz[u]=1; mxsiz[u]=0;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            centroid(v.v,u);
            siz[u]+=siz[v.v];
            mxsiz[u]=max(mxsiz[u],siz[v.v]);
        }
    }
    mxsiz[u]=max(mxsiz[u],sum-siz[u]);
    if(!rt||mxsiz[u]<mxsiz[rt]){
        rt=u;
    }
}
void distance(int u,int fa){
    isk[dis[u]]++;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            dis[v.v]=(dis[u]+v.w)%3;
            distance(v.v,u);
        }
    }
}
int calc(int u,int Dis){
    isk[0]=isk[1]=isk[2]=0;
    dis[u]=Dis%3;
    distance(u,0);
    return isk[0]*isk[0]+isk[1]*isk[2]*2;
}
void dfz(int u,int fa){
    vis[u]=1;
    ans+=calc(u,0);
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            ans-=calc(v.v,v.w);
            sum=siz[v.v];
            rt=0;
            mxsiz[rt]=inf;
            centroid(v.v,u);
            dfz(rt,0);
        }
    }
}

signed main(){
    cin>>n;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        add(u,v,w%3);
    }

    sum=n;
    rt=0;
    mxsiz[rt]=inf;
    centroid(1,0);
    dfz(rt,0);
    int gcd=__gcd(ans,n*n);
    cout<<ans/gcd<<"/"<<n*n/gcd<<'\n';
    return 0; 
}

P4886 快递员

给出 \(m\) 个点对 \((u_i,v_i)\),求一个点 \(x\),使得 \(dis(x,u_i)+dis(x,v_i)\) 的最大值最小。

\(n,m\le 10^5\)

设当前根为 \(rt\),则有以下结论:

  • \(rt\) 在最远点对的路径上,则答案不可能更小;
  • 若存在两个最远点对在 \(rt\) 的不同子树内,则答案不可能更小;
  • 若以上两条都不满足,则将 \(rt\) 移动到唯一一个包含最远点对的子树内,答案更优。

如上模拟即可。

时间复杂度 \(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+3;
const int inf=0x3f3f3f3f;
#define int long long
int n,m;
struct edge{
    int v,w;
    edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
struct{
    int u,v;
}a[maxn];
vector<int>d;
int sub[maxn];
bool vis[maxn];
int sum;
int ans=inf;
void add(int u,int v,int w){
    e[u].emplace_back(edge(v,w));
    e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
    siz[u]=1; mxsiz[u]=0;
    for(edge v:e[u]){
        if(v.v!=fa&&!vis[v.v]){
            centroid(v.v,u);
            siz[u]+=siz[v.v];
            mxsiz[u]=max(mxsiz[u],siz[v.v]);
        }
    }
    mxsiz[u]=max(mxsiz[u],sum-siz[u]);
    if(!rt||mxsiz[u]<mxsiz[rt]){
        rt=u;
    }
}
void distance(int u,int fa,int root){
    sub[u]=root;
    for(edge v:e[u]){
        if(v.v!=fa){
            dis[v.v]=dis[u]+v.w;
            distance(v.v,u,root);
        }
    }
}
void dfz(int u,int fa){
    // cout<<u<<'\n'/;
    if(vis[u]){
        cout<<ans;
        exit(0);
    }
    vis[u]=1;
    dis[u]=0;
    for(edge v:e[u]){
        dis[v.v]=v.w;
        distance(v.v,u,v.v);
    }
    
    int lst=0,mx=0;
    for(int i=1;i<=m;i++){
        if(dis[a[i].u]+dis[a[i].v]>mx){
            d.clear();
            mx=dis[a[i].u]+dis[a[i].v];
            d.emplace_back(i);
        }else if(dis[a[i].u]+dis[a[i].v]==mx){
            d.emplace_back(i);
        }
    }
    if(mx<ans) ans=mx;
    for(int i:d){
        if(sub[a[i].u]!=sub[a[i].v]){
            cout<<ans;
            exit(0);
        }
        if(!lst) lst=sub[a[i].u];
        if(sub[a[i].u]!=lst){
            cout<<ans;
            exit(0);
        }
    }
    d.clear();
    sum=siz[lst];
    rt=0;
    // mxsiz[rt]=inf;
    centroid(lst,0);
    dfz(rt,0);
}

signed main(){
    cin>>n>>m;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        add(u,v,w);
    }
    for(int i=1;i<=m;i++){
        cin>>a[i].u>>a[i].v;
    }
    sum=n;
    rt=0;
    mxsiz[rt]=inf;
    centroid(1,0);
    dfz(rt,0);
    return 0; 
}
posted @ 2024-08-14 15:08  view3937  阅读(9)  评论(0编辑  收藏  举报
Title