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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}