点分治

树上的算法真的很有意思……哈哈。

给一棵边带权树,问两点之间的距离小于等于K的点对有多少个。

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。

如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。

在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。

树的重心的算法可以线性求解。


[cpp]  view plain copy
  1. #include <cstdio>  
  2. #include <algorithm>  
  3. #include <vector>  
  4. #include <cstring>  
  5. using namespace std;  
  6. #define N 10009  
  7. struct node {  
  8.     int v, l;  
  9.     node() {};  
  10.     node(int _v, int _l): v(_v), l(_l) {};  
  11. };  
  12. vector<node> g[N];  
  13. int n, k, size, s[N], f[N], root, d[N], K, ans;  
  14. vector<int> dep;  
  15. bool done[N];  
  16. void getroot(int now, int fa) {  
  17.     int u;  
  18.     s[now] = 1; f[now] = 0;  
  19.     for (int i=0; i<g[now].size(); i++)  
  20.         if ((u = g[now][i].v) != fa && !done[u]) {  
  21.             getroot(u, now);  
  22.             s[now] += s[u];  
  23.             f[now] = max(f[now], s[u]);  
  24.         }  
  25.     f[now] = max(f[now], size-s[now]);  
  26.     if (f[now] < f[root]) root = now;  
  27. }  
  28. void getdep(int now, int fa) {  
  29.     int u;  
  30.     dep.push_back(d[now]);  
  31.     s[now] = 1;  
  32.     for (int i=0; i<g[now].size(); i++)  
  33.         if ((u = g[now][i].v) != fa && !done[u]) {  
  34.             d[u] = d[now] + g[now][i].l;  
  35.             getdep(u, now);  
  36.             s[now] += s[u];  
  37.         }  
  38. }  
  39. int calc(int now, int init) {  
  40.     dep.clear(); d[now] = init;  
  41.     getdep(now, 0);  
  42.     sort(dep.begin(), dep.end());  
  43.     int ret = 0;  
  44.     for (int l=0, r=dep.size()-1; l<r; )  
  45.         if (dep[l] + dep[r] <= K) ret += r-l++;  
  46.         else r--;  
  47.     return ret;  
  48. }  
  49. void work(int now) {  
  50.     int u;  
  51.     ans += calc(now, 0);  
  52.     done[now] = true;  
  53.     for (int i=0; i<g[now].size(); i++)  
  54.         if (!done[u = g[now][i].v]) {  
  55.             ans -= calc(u, g[now][i].l);  
  56.             f[0] = size = s[u];  
  57.             getroot(u, root=0);  
  58.             work(root);  
  59.         }  
  60. }  
  61. int main() {  
  62.   
  63.     while (scanf("%d%d", &n, &K) == 2) {  
  64.         if (n == 0 && K == 0) break;  
  65.         for (int i=0; i<=n; i++) g[i].clear();  
  66.         memset(done, falsesizeof(done));  
  67.   
  68.         int u, v, l;  
  69.         for (int i=1; i<n; i++) {  
  70.             scanf("%d%d%d", &u, &v, &l);  
  71.             g[u].push_back(node(v, l));  
  72.             g[v].push_back(node(u, l));  
  73.         }  
  74.         f[0] = size = n;  
  75.         getroot(1, root=0);  
  76.         ans = 0;  
  77.         work(root);  
  78.         printf("%d\n", ans);  
  79.     }  
  80.     return 0;  
  81. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值