巨木之森(树的路径,贪心)

tech2023-06-23  109

巨木之森

巨木之森(树的路径,贪心)题意数据范围思路代码

巨木之森(树的路径,贪心)

巨木之森

题意

给定一棵 n n n个结点的树, m m m块钱。定义花费为从一个点出发遍历整棵树所经过的路径和。求最多能选择多少个不同的起点使得花费总和 ≤ m \leq m m

数据范围

n ≤ 1 0 5 , m ≤ 1 0 18 , w i ≤ 1 0 8 n \leq 10^5, m \leq 10^{18},w_i \leq 10^8 n105,m1018,wi108

思路

这道题问的是路径和,所以要从每条边计算几次入手。可以将树画成一条链,链的两侧有若干子树的形状,这样问题会变得更加直观。 我们很容易发现,在一个花费中,一条链被计算 1 1 1次,其余边权被计算 2 2 2次。假设这条链的长度为 l e n len len,整棵树所有边权和为 t o t a l total total,则花费即为 l e n + 2 ∗ ( t o t a l − l e n ) = 2 ∗ t o t a l − l e n . len+2*(total - len) = 2 * total - len. len+2(totallen)=2totallen. 我们根据式子,可以发现花费只与链的长度有关。因此,为了使每次花费最少,我们可以选取以起点为一个端点的最长链。求出以每个点为起点的最长链,在从大到小遍历一遍,统计出答案,问题就解决了。 代码的书写就是个套模板的过程。

代码

#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; const int N = 100010, M = 2 * N; const ll INF = 2e18; int n; ll m; int h[N], e[M], ne[M], idx; ll w[M]; ll d1[N], d2[N], p1[N], up[N], len[N]; bool is_leaf[N]; void add(int a,int b,ll c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; } ll dfs_d(int u, int father) { d1[u] = d2[u] = -INF; for(int i=h[u];~i;i=ne[i]){ int j = e[i]; if(j==father) continue; ll d = dfs_d(j, u) + w[i]; if(d>=d1[u]){ d2[u] = d1[u], d1[u] = d; p1[u] = j; } else if(d>d2[u]) d2[u] = d; } if(d1[u]==-INF){ d1[u] = d2[u] = 0; is_leaf[u] = true; } return d1[u]; } void dfs_u(int u, int father) { for(int i=h[u];~i;i=ne[i]){ int j = e[i]; if(j==father) continue; if(p1[u]==j) up[j] = max(up[u], d2[u]) + w[i]; else up[j] = max(up[u], d1[u]) + w[i]; dfs_u(j, u); } } int main() { scanf("%d%lld",&n,&m); memset(h,-1,sizeof(h)); ll tot = 0; for(int i=1;i<n;i++){ int a,b; ll c; scanf("%d%d%lld",&a,&b,&c); add(a,b,c), add(b,a,c); tot += c; } tot *= 2; dfs_d(1, -1); dfs_u(1, -1); len[1] = d1[1]; for(int i=2;i<=n;i++){ if (is_leaf[i]) len[i] = up[i]; else len[i] = max(d1[i], up[i]); } sort(len+1,len+1+n); int ans = 0; for(int i=n;i>=1;i--){ if(m<=0) break; else{ m -= (tot - len[i]); if(m>=0) ans ++; } } printf("%d\n",ans); return 0; }
最新回复(0)