POJ1741 Tree(点分治)

嘟嘟嘟

没错,这一道最经典的点分治模板题。
题意:求树上两点间距离\(\leqslant k\)的点对个数。

点分治这东西我好早就听说了,然后一两个月前也学了一下,不过只是刷了个模板,没往深处学。
对于这道题,就说说大概的步骤吧。
1.找重心:一遍\(dfs\)即可。
2.求出每一个子树中的点到重心的距离。并且记录这个点属于哪一棵子树。
3.把上述的点存下来,按距离从小到大排序。
4.统计答案。采用双指针,\(i\)从头开始,\(j\)从尾开始。这样每一个\(i\)对答案的贡献是\(j - i - num[point_i]\)\(num[point_i]\)表示的是\(i\)所在的子树有多少个。(为了减去属于相同子树的贡献)
5.递归到每一个子树中统计答案。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define rg register
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 4e4 + 5;
inline ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
inline void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}

int n, k;
struct Edge
{
  int nxt, to, w;
}e[maxn << 1];
int head[maxn], ecnt = -1;
void addEdge(int x, int y, int w)
{
  e[++ecnt] = (Edge){head[x], y, w};
  head[x] = ecnt;
}

bool out[maxn];
int Siz, siz[maxn], Max[maxn];
void dfs1(int now, int _f, int &cg)
{
  siz[now] = 1; Max[now] = -1;
  for(int i = head[now], v; i != -1; i = e[i].nxt)
    {
      if(!out[v = e[i].to] && v != _f)
    {
      dfs1(v, now, cg);
      siz[now] += siz[v];
      Max[now] = max(Max[now], siz[v]);
    }
    }
  Max[now] = max(Max[now], Siz - siz[now]);
  if(!cg || Max[now] < Max[cg]) cg = now;
}

struct Node
{
  int dis, bel;
  bool operator < (const Node& oth)const
  {
    return dis < oth.dis;
  }
}a[maxn];
int cnt = 0;
void dfs2(int now, int _f, int dis, int x, int cg)
{
  siz[now] = 1;
  a[++cnt] = (Node){dis, x};
  for(int i = head[now], v; i != -1; i = e[i].nxt)
    {
      if(!out[v = e[i].to] && v != _f)
    {
      dfs2(v, now, dis + e[i].w, now == cg ? v : x, cg);
      siz[now] += siz[v];
    }
    }
}

int num[maxn], ans = 0;
void solve(int now)
{
  int cg = 0; cnt = 0;
  dfs1(now, 0, cg);
  dfs2(cg, 0, 0, 0, cg);
  sort(a + 1, a + cnt + 1);
  for(int i = head[cg]; i != -1; i = e[i].nxt)
    if(!out[e[i].to]) num[e[i].to] = 0;
  for(int i = 1; i <= cnt; ++i) num[a[i].bel]++;
  for(int i = 1, j = cnt; i <= j; ++i)
    {
      num[a[i].bel]--;
      while(a[i].dis + a[j].dis > k && i <= j) num[a[j--].bel]--;
      if(i > j) break;
      ans += j - i - num[a[i].bel];
    }
  out[cg] = 1;
  for(int i = head[cg], v; i != -1; i = e[i].nxt)
    if(!out[v = e[i].to]) Siz = siz[v], solve(v);
}

int main()
{
  Mem(head, -1);
  n = read();
  for(int i = 1; i < n; ++i)
    {
      int x = read(), y = read(), w = read();
      addEdge(x, y, w); addEdge(y, x, w);
    }
  k = read();
  ans = 0; Siz = n;
  solve(1);
  write(ans), enter;
  return 0;
}

转载于:https://www.cnblogs.com/mrclr/p/10032664.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值