tree --- 点分治

传送门:洛谷4178


题目描述

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K


分析

  点分治的经典例题
  假设当前根节点为p,对于满足条件的路径,无非有两种:经过p和不经过p.这也是分治的依据.那么,要计算路径数的话,只需要计算经过点p的路径数(存在线性算法),不经过的递归处理即可
  具体流程:
    1. 找出树的重心作为当前的根节点—保证复杂度
    2. 计算经过根结点的路径
    3. 标记该点(视为删除),递归处理子树

  计算的路径要满足两个条件a.dis[x]+dis[y] >= k b.belong[x] != belong[y](dis[x]为x到根节点的距离,belong[x]表示x所属的子树)
  直接求的话可能会有点麻烦(但也有,参考《算法竞赛进阶指南》),对此可以考虑间接求解:用只满足a的数量-满足a但不满足b的数量(在每一颗子树中求解即可)
  对于只满足a的路径数,可以先将dis数组中<=k的取出来排个序,用两个指针L,R分别从前/后开始扫描数组,易知,随着l的增加,r单调递减.而以l为端点的路径数量为r- l;


代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

#define IL inline
#define open(s) freopen(s".in","r",stdin); freopen(s".out","w",stdout);
#define close fclose(stdin); fclose(stdout);

using namespace std;

IL int read()
{
    char c = getchar();
    int sum = 0 ,k = 1;
    for(;'0' > c || c > '9'; c = getchar())
        if(c == '-') k = -1;
    for(;'0' <= c && c <= '9'; c = getchar()) sum = sum * 10 + c - '0';
    return sum * k;
}

const int maxn = 40000 + 5;

struct Edge
{
    int to, nxt, w;
    IL Edge(int to_ = 0, int nxt_ = 0, int w_ = 0)
    {
        to = to_; nxt = nxt_; w = w_;
    }
}edge[maxn << 1];
int cnt;
int head[maxn];
IL void add(int u, int v, int w)
{
    edge[++cnt] = Edge(v, head[u], w); head[u] = cnt;
    edge[++cnt] = Edge(u, head[v], w); head[v] = cnt;
}

int n, k;
int tot;
bool use[maxn];
int dis[maxn];
int size[maxn];

IL void get_root(int u, int pre, int &root, int &kk, int m)
{
    size[u] = 1;
    int tmp = 0;
    for(int i = head[u], v; i; i = edge[i].nxt)
    {
        v = edge[i].to;
        if(v == pre || use[v]) continue;
        get_root(v, u, root, kk, m);
        size[u] += size[v];
        if(size[v] > tmp) tmp = size[v];
    }
    if(m - size[u] > tmp) tmp = m - size[u];
    if(tmp < kk) { kk = tmp; root = u; }
}

IL void get_dis(int u, int pre, int d)
{
    if(d > k) return ;
    dis[++tot] = d;
    for(int i = head[u], v; i; i = edge[i].nxt)
    {
        v = edge[i].to;
        if(v == pre || use[v]) continue;
        get_dis(v, u, d + edge[i].w);
    }
}

IL int calc(int p, int x)
{
    tot = 0;
    get_dis(p, 0, x);
    sort(dis + 1, dis + tot + 1);
    int l = 1, r = tot;
    int sum = 0;
    for(; l < r; ++l)
    {
        for(; l < r && dis[r] + dis[l] > k; --r);
        sum += r - l;
    }
    return sum;
}

IL int solve(int p, int m)
{
    int root = -1, kk = n;
    get_root(p, p, root, kk, m);//找重心
    use[root] = 1;//标记
    int sum = calc(root, 0);//先计算全部

    for(int i = head[root], v; i; i = edge[i].nxt)
    {
        v = edge[i].to;
        if(use[v]) continue;
        sum -= calc(v, edge[i].w);//减去重复的
        sum += solve(v, size[v]);//递归处理
    }
    return sum;
}

int main()
{
    open("4178")

    n = read();
    for(int i = 1, x, y, z; i < n; ++i)
    {
        x = read(); y = read(); z = read();
        add(x, y, z);
    }
    k = read();

    printf("%d\n", solve(1, n));

    close
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_27121257/article/details/81217447