题目链接:
[POJ 1741]Tree[树分治]
题意分析:
统计树上一共有多少对结点,两者之间的最短距离不超过k。
解题思路:
1e4个点,暴搜肯定是不行的。这里我们采用树分治来解决这个问题。
大体思路:将树不断地划分成两块——>以划分点作为“中间点”(我们称为重心),统计该点周围点到该点的距离ds[],统计ds[]+ds[]小于等于k的对数——>回归上层继续计算
总结来说就是:划分——>计算。类比下归并排序即可。
根据思路就有了两个问题:1.重心怎么找?2.只统计两侧点会不会丢失情况?
1.规定重心:删去该点后最大子图顶点数最少的结点。根据定义递归查找即可。
2.设S点为重心,划分出了两个块,那么总共有三种情况:1)点对在同一个连通块;2)点对在不同的连通块;3)点对之中有一个为S点;
对于3)情况,我们引入一个虚拟点,到S的距离为0,情况转化为2)。对于情况1),我们在划分子树的过程中,又变成了情况2)或3),所以只要周围点互相枚举即可。由于我们的统计方法是把ds排序后,进行比对,就有可能枚举了两个在同一个块的点,所以要预先剪掉。
个人感受:
一个晚上+凌晨的时间,终于把它搞懂了= =。说白了就是归并的分而治之,然而看得着实不易啊TAT
具体代码如下:
#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<string>
#define ll long long
#define pii pair<int, int>
#define pr(x) cout << #x << " = " << (x) << '\n';
using namespace std;
const int INF = 0x7f7f7f7f;
const int MAXN = 1e4 + 111;
struct Edge {
int to, next, w;
}edge[2 * MAXN];
int head[MAXN], tot;
int n, k, ans;
int subSize[MAXN];
bool vis[MAXN];
void init() {
ans = tot = 0;
memset(vis, 0, sizeof vis);
memset(head, -1, sizeof head);
}
void add_edge(int u, int v, int w) {
edge[tot].to = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot++;
}
int compute_subSize(int u, int p) {
int c = 1;
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (v != p && !vis[v]) c += compute_subSize(v, u);
}
return subSize[u] = c;
}
// 寻找删除该点后最大子树的顶点数最少的点
pii search_it(int u, int p, int t) {
pii ret = make_pair(INF, -1);
int s = 1, m = 0;
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (v != p && !vis[v]) {
ret = min(ret, search_it(v, u, t));
m = max(m, subSize[v]);
s += subSize[v];
}
}
m = max(m, t - s); // t - s:包含u点的子树顶点个数
return min(ret, make_pair(m, u));
}
void getdis(int u, int p, int d, vector<int> &ds) {
ds.push_back(d);
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (v != p && !vis[v]) getdis(v, u, d + edge[i].w, ds);
}
}
int countAns(vector<int> &ds) {
int ret = 0;
sort(ds.begin(), ds.end());
int i = 0, j = ds.size() - 1;
while (i < j) {
while (ds[i] + ds[j] > k && i < j) --j;
ret += j - i;
++i;
}
return ret;
}
void solve(int v) {
// 找重心
compute_subSize(v, -1);
int s = search_it(v, -1, subSize[v]).second;
vis[s] = 1;
// 继续划分
for (int i = head[s]; ~i; i = edge[i].next) {
if (!vis[edge[i].to]) solve(edge[i].to);
}
// 计算个数
vector<int> ds;
ds.push_back(0); // 距离s为0的虚拟点
for (int i = head[s]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v]) continue;
vector<int> tds;
getdis(v, s, edge[i].w, tds);
ans -= countAns(tds);
ds.insert(ds.end(), tds.begin(), tds.end());
}
ans += countAns(ds);
vis[s] = 0; // 复原,为大子树计算服务2333
}
int main()
{
#ifdef LOCAL
freopen("C:\\Users\\apple\\Desktop\\in.txt", "r", stdin);
#endif
while (~scanf("%d%d", &n, &k) && (n | k)) {
init();
int u, v, l;
for (int i = 1; i < n; ++i) {
scanf("%d%d%d", &u, &v, &l);
add_edge(u, v, l);
add_edge(v, u, l);
}
solve(1);
printf("%d\n", ans);
}
return 0;
}