POJ 1741 Tree (树分治之点分治)

题目大意:

给出一棵树, 点数n <= 10000, 给出每条边的权值和K, 求满足两点距离不超过K的点对数


大致思路:

马上就要区域赛了...走前看点点分治的题...遵循昊哥的教导

这个题就是点分治很裸的一个模型了

所谓点分治, 每次将问题递归到子树上解决,为了保证递归层数, 每次选取树的重心来进行分割, 可以证明这样的问题递归层数不会超过O(logn)

那么剩下的都直接看的模板了....

感觉这个模板好几个dfs之间的关联性好强...有种环环相扣的紧凑感

由于每次递归下去产生的子树的结果都需要按照到当前根节点的距离进行排序, 于是整体时间复杂度是O(n(logn)^2)


代码如下:

Result  :  Accepted     Memory  :  868 KB     Time  :  235 ms

/*
 * Author: Gatevin
 * Created Time:  2015/10/13 22:29:52
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 10010

struct Edge
{
    int u, v, nex, w;
    Edge(){}
    Edge(int _u, int _v, int _nex, int _w)
    {
        u = _u, v = _v, nex = _nex, w = _w;
    }
};

Edge edge[maxn << 1];
int head[maxn];
int E, ans;
int n, K;

void add_Edge(int u, int v, int w)
{
    edge[++E] = Edge(u, v, head[u], w);
    head[u] = E;
}

int del[maxn];
int root;
int num;
int mx[maxn];
int size[maxn];
int mi;
int dis[maxn];

void dfs_size(int now, int father)//计算每个子树的结点对应的子树的结点数
{
    size[now] = 1;
    mx[now] = 0;
    for(int i = head[now]; i + 1; i = edge[i].nex)
    {
        int v = edge[i].v;
        if(v != father && !del[v])
        {
            dfs_size(v, now);
            size[now] += size[v];
            if(size[v] > mx[now]) mx[now] = size[v];
        }
    }
}

void dfs_root(int r, int now, int father)//寻找重心
{
    if(size[r] - size[now] > mx[now]) mx[now] = size[r] - size[now];
    if(mx[now] < mi) mi = mx[now], root = now;
    for(int i = head[now]; i != -1; i = edge[i].nex)
    {
        int v = edge[i].v;
        if(v != father && !del[v]) dfs_root(r, v, now);
    }
}

void dfs_dis(int now, int d, int father)//求出距离
{
    dis[num++] = d;
    for(int i = head[now]; i + 1; i = edge[i].nex)
    {
        int v = edge[i].v;
        if(v != father && !del[v]) dfs_dis(v, d + edge[i].w, now);
    }
}

int calc(int now, int d)
{
    int ret = 0;
    num = 0;
    dfs_dis(now, d, 0);
    sort(dis, dis + num);
    int i = 0, j = num - 1;
    while(i < j)//两点法扫描
    {
        while(dis[i] + dis[j] > K && i < j) j--;
        ret += j - i;
        i++;
    }
    return ret;
}

void dfs(int now)//递归求解当前以now为根的子树的解(两个点不来自于同一个切分后的子树)
{
    mi = n;//min
    dfs_size(now, 0);//处理出以now为根的子树各个节点的子树大小, 并用mx[u]表示切掉u之后分出的子树的最大size
    dfs_root(now, now, 0);//寻找出重心
    ans += calc(root, 0);//计算这个子树中任意两个点之间dis和 <= k的对数(可能来自切分后的同一子树)
    del[root] = 1;
    for(int i = head[root]; i + 1; i = edge[i].nex)
    {
        int v = edge[i].v;
        if(!del[v])
        {
            ans -= calc(v, edge[i].w);//容斥一下减去来自同一棵子树的情况
            dfs(v);//递归到子树的解
        }
    }
}

void init()
{
    memset(del, 0, sizeof(del));
    memset(head, -1, sizeof(head));
    E = ans = 0;
}

void input()
{
    int u, v, w;
    for(int i = 1; i < n; i++)
    {
        scanf("%d %d %d", &u, &v, &w);
        add_Edge(u, v, w);
        add_Edge(v, u, w);
    }
}

void solve()
{
    init();
    input();
    dfs(1);//以1为根dfs
    printf("%d\n", ans);
}

int main()
{
    while(scanf("%d %d", &n, &K), n || K)
        solve();
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值