点分治
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;
}