点分治

1.1 算法简介

点分治划分了一些分治部分,并将每一条树上路径 \((i, j)\) 分类到了有且仅有一个部分,每个部分的路径都经过了同一个点,对每一个部分进行处理之后可以得到所有路径的信息。

对于序列区间分治(cdq 分治)而言我们二分序列,使得分治部分的个数为 \(O(n)\),且区间 \([l, r], mid\) 包含左端点在 \([l, mid]\),右端点在 \((mid, r]\) 内的区间,一共有 \(\log n\) 层,每一层长度加起来都是 \(n\),并且每一个区间都跨过 \(mid\) 使得可以将每一个分到这部分的区间剖成两半,如果将前缀信息和后缀信息 \(O(n \log^k n)\) 离线合并,那么就可以 \(O(n \log^{k+1} n)\) 做完事情。

对于点分治,也是类似的。每一个分治部分是一个树上连通块并包含一个“根”,每一对路径都跨过根 \(root\)。我们也是分出 \(\log n\) 个层,使得每一层的所有部分子树不交并且其并集大小为 \(O(n)\)。一条路径可以被拆成两个“点到根的路径”(降维),如果能对两个路径快速离线合并也可以快速做完事情。

1.2 算法细节

每一次,我们选择树的重心作为根,然后传到下一层的是每一个子树。

  • 找重心
    先随便指定一个根。只需一次 dfs 找到所有点的 sz,如果 \(\min (\min \limits_{j \in son_i} sz_j, sz_{root} - sz_i) \le \lfloor\cfrac{sz_{root}}{2}\rfloor\) 即判定 \(i\) 是树根。\(sz_{root}\) 在这里是整棵树的大小,只需要一次 dfs 即可找到重心。

  • 传子树
    其实只需要开一个数组 \(vis\),令做过根的点的 \(vis = 1\) 即可。传子树的时候传当前根的儿子,它一定在那个子树内,可以让它做随便指定的那个根。然后 dfs 的时候忽略 \(vis=1\) 的点即可。

对于合并“前缀路径”的过程:

  • 处理所有在这个分治部分的路径
    先把所有前缀路径对合并,然后这个过程中多算了所有 \(i,j\) 在根的相同子树上的路径对,因此需要去每一个子树上删掉。

CF293E Close Vertices

【题意】
一棵树,边有边权,定义一个路径的长度为其经过所有边的数量,一个路径的权值为其经过所有边的权值和,求长度 \(\le l\),权值 \(\le w\) 的点的个数。

\(n \le 10^5, w \le 10^9\)

【分析】
考虑点分治。对于前缀路径的合并,按照前缀路径的长度排序,然后双指针加一个树状数组即可。


int n, lyh, kjy; vector<pii> e[100200]; bool vis[100200]; int dep[100200], sz[100200], mx[100200], zxdep[100200], zxwei[100200]; 
vector<int> pt; vector<pii> dis; int ans=0; int lsh[100200]; 
struct FWT {
    int a[100020];  int v; 
    void build(int _v) {v = _v; f(i, 1, v) a[i] = 0; }
    int lowbit(int x) {return x & -x; }
    void add(int x, int k){while(x <= v) {a[x]+=k;x+=lowbit(x);}}
    int query(int x){int res=0;while(x > 0) {res+=a[x];x-=lowbit(x); }return res;} 
}fwt;
void dfs(int now,int fa){
    pt.push_back(now);dep[now]=dep[fa]+1; sz[now]=1; mx[now]=0; 
    for(pii i:e[now]) {if(i.first!=fa&&!vis[i.first]) {dfs(i.first,now); cmax(mx[now],sz[i.first]);sz[now]+=sz[i.first];}}
}
void zxdfs(int now,int fa){
    dis.push_back({zxdep[now],zxwei[now]});
    for(pii i:e[now]) {if(i.first!=fa&&!vis[i.first]) {zxdep[i.first]=zxdep[now]+1;zxwei[i.first]=zxwei[now]+i.second;zxdfs(i.first,now);}}
}
void deal(int k, int yh, int jy) {
    auto cmp = [=](pii x,pii y){return x.first < y.first;}; sort(dis.begin(),dis.end(),cmp); f(i,0,(int)dis.size()-1)lsh[i+1]=dis[i].second;
    sort(lsh+1,lsh+(int)dis.size()+1);int cnt=unique(lsh+1,lsh+(int)dis.size()+1)-lsh-1; fwt.build(cnt); 
    f(i,0,(int)dis.size()-1)fwt.add(lower_bound(lsh+1,lsh+cnt+1,dis[i].second)-lsh,1);
    for(int l=0,r=(int)dis.size()-1; l<=r; l++){
        while(r>=l&&dis[l].first+dis[r].first>lyh-yh){fwt.add(lower_bound(lsh+1,lsh+cnt+1,dis[r].second)-lsh,-1);r--;}
        int redu = kjy - jy - dis[l].second; int que = prev(upper_bound(lsh+1,lsh+cnt+1,redu))-lsh;ans+=k*fwt.query(que);
        fwt.add(lower_bound(lsh+1,lsh+cnt+1,dis[l].second)-lsh,-1);
    }
}
void dfz(int root){
    pt.clear(); dfs(root,0); int zx = 0; for(int i : pt) {cmax(mx[i], sz[root]-sz[i]); if(mx[i] <= sz[root]/2)zx=i;}
    dis.clear(); zxdep[zx]=zxwei[zx]=0; zxdfs(zx, 0); deal(1,0,0); ans--;
    vis[zx]=1; for(pii i:e[zx]) {if(vis[i.first])continue;dis.clear(); zxdep[i.first]=zxwei[i.first]=0;zxdfs(i.first,0);deal(-1,2,i.second*2);}
    for(pii i : e[zx]) if(!vis[i.first]) dfz(i.first);
}
signed main() {
    cin >> n >> lyh >> kjy;
    f(i, 1, n - 1) {int p,m;cin>>p>>m;e[p].push_back({i + 1, m}); e[i+1].push_back({p,m});}
    dfz(1); cout << ans << endl; 
    return 0;
}
posted @ 2022-10-07 11:28  OIer某罗  阅读(25)  评论(1编辑  收藏  举报