POJ 1741(男人八题)

                POJ 1741(男人八题)解题报告。

Tree
Time Limit:1000MS Memory Limit:30000KB 64bit IO Format:%I64d & %I64u

Description
Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.
Input
The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.
Output
For each test case output the answer on a single line.
Sample Input
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
Sample Output
8

大意就是给你一个n个节点树,每条边有个权值,求点对(i, j) 满足i到j的最短距离小于等于k 且i小于j的个数.
这道题,最初看了下数据范围,1w,果断n^2暴力试了下,然后果断的tle了。
正解是,用到树的分治思想中的点分治。
我们这么想,在树中,任意两点的路,有且只有一条,且这条路就是最短路。
回到问题,我们要求的是满足dis[i][j] <= k 且 i < j 的点对数,我们先假设一个跟
root, 那么这两点要么经过这个root,要么不经过这个root, 如果它经过了这个root,
那么dis[i] + dis[j] (dis[v] 表示v节点距离root节点的距离) <= k的话,这就是一个合法点对, 如果不经过root, 那他肯定是走了重复的一段路,且一定会经过这个root的子树的根节点,那么这个点对是重复计算了的,我们需要减去。这个不合法点对满足两个条件:

1:这两个点一定经过了这个root的某子树的根节点
假设经过的root的子树的根节点标号为root1
2:这两个点到root1的距离 dis1[i] + dis1[j] + 2 * cost[root1][root2] <= k(满足这个的话,才会在前面计算的时候重复)
那么这个问题就很好解决了,然后我果断的去写了一发,照样tle.
因为没有考虑到一个问题,就是 一条链的极限情况,所以我们每次递归求ans的时候,必须要查找当前子树的重心,这样就行了(诶,真尼玛麻烦)
Ac代码和详解如下:

//
//  Created by Running Photon on 2015-09-06
//  Copyright (c) 2015 Running Photon. All rights reserved.
//
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <sstream>
#include <set>
#include <vector>
#include <stack>
#define ALL(x) x.begin(), x.end()
#define INS(x) inserter(x, x,begin())
#define ll long long
#define CLR(x) memset(x, 0, sizeof x)
#define MAXN 9999
#define MAXSIZE 10
#define DLEN 4
using namespace std;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 2e4 + 10;
const int maxv = 1e4 + 10;
const double eps = 1e-9;


inline int read() {
    char c = getchar();
    int f = 1;
    while(!isdigit(c)) {
        if(c == '-') f = -1;
        c = getchar();
    }
    int x = 0;
    while(isdigit(c)) {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * f;
}
int n, k, ans, tot;
int pnt[maxn], cnt, nxt[maxn], cost[maxn], head[maxv], sum;
void add_edge(int u, int v, int val) {
    pnt[cnt] = v;
    cost[cnt] = val;
    nxt[cnt] = head[u];
    head[u] = cnt++;
}
int vis[maxv], f[maxv], dis[maxv], res[maxv], maxson[maxv];
//找到当前树的重心,并且把它当做根
void findRoot(int u, int fa, int &root) {
    f[u] = 1; maxson[u] = 0;
    for(int i = head[u]; ~i; i = nxt[i]) {
        int v = pnt[i];
        if(v == fa || vis[v]) continue;
        findRoot(v, u, root);
        f[u] += f[v];
        maxson[u] = max(f[v], maxson[u]);
    }
    maxson[u] = max(maxson[u], sum - f[u]);
    if(maxson[u] < maxson[root]) {
        root = u;
    }
}
// 找到距离根节点的距离
void getdis(int u, int fa) {
    res[tot++] = dis[u];
//  printf("dis[%d] = %d\n", u, dis[u]);
    for(int i = head[u]; ~i; i = nxt[i]) {
        int v = pnt[i];
        if(v == fa || vis[v]) continue;
        dis[v] = dis[u] + cost[i];
        getdis(v, u);
    }
}
//快速计算合法点对数
int cal() {
    int tmp = 0;
    int l = 0, r = tot - 1;
    while(l < r) {
        if(res[l] + res[r] <= k) {
            tmp += r - l;
            l++;
        }
        else r--;
    }
    return tmp;
}
//分治 分布求解,先加后减去重复的
void dfs(int root) {
    tot = 0;
    int u = 0;
    findRoot(root, root, u);
    dis[u] = 0;
//  printf("root = %d\n", u);
    getdis(u, u);
    sort(res, res + tot);
    ans += cal();
    vis[u] = 1;
    for(int i = head[u]; ~i; i = nxt[i]) {
        int v = pnt[i];
        if(vis[v]) continue;
        tot = 0;
        dis[v] = cost[i];
        getdis(v, v);
        sort(res, res + tot);
        ans -= cal();
        sum = f[v];
        dfs(v);
    }
}
int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
//  freopen("out.txt","w",stdout);
#endif
    while(scanf("%d%d", &n, &k) != EOF && (n + k)) {
        memset(head, -1, sizeof head);
        sum = n;
        cnt = 0; ans = 0; CLR(vis);
        for(int i = 1; i < n; i++) {
            int u, v, val;
            u = read(), v = read(), val = read();
            add_edge(u, v, val);
            add_edge(v, u, val);
        }
        maxson[0] = inf;
        dfs(1);
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值