题意分析:
统计树上一共有多少对结点,两者之间的最短距离不超过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; }