【NOI2003T4】逃学的小孩-贪心+树形DP求直径+倍增LCA

本文介绍了一种利用树形DP求解树直径的方法,并通过枚举树上任意一点来找到特定条件下的最优解。此外,还介绍了如何使用倍增LCA算法计算树上两点间的距离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试地址:逃学的小孩

做法:通过各种证明可以得到一个贪心的思路:当A,B分别为树直径的两端时,一定存在最优答案。求直径我们用树形DP可以O(n)求出:求出以某一个点为根的子树上与其距离最远和次远的点及根与它们的距离,而且要保证根到这两点的路径不相交,这样就可以求出通过某点的最长路径,然后再枚举每一个点求最大值就是树中的最长路径,也就是直径。接下来枚举C,则答案为max(dis(A,B)+min(dis(A,C),dis(B,C))),求树上两点间的距离可以用倍增LCA做,时间复杂度是O(nlogn)的。注意一些值可能大于32位整数所能存下的最大值,所以要使用long long型存储。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int mxp[200010],smxp[200010],dep[200010],tot=0;
ll mx[200010],smx[200010],dd[200010],dis,ans;
int n,m,fa[200010][20],first[200010]={0};
struct edge {int v,next;ll d;} e[400010];

void insert(int a,int b,int d)
{
  e[++tot].v=b,e[tot].d=d,e[tot].next=first[a],first[a]=tot;
}

void dfs(int v)
{
  mx[v]=smx[v]=0,mxp[v]=smxp[v]=v;
  for(int i=first[v];i;i=e[i].next)
    if (e[i].v!=fa[v][0])
	{
	  fa[e[i].v][0]=v;
	  dep[e[i].v]=dep[v]+1;
	  dd[e[i].v]=dd[v]+e[i].d;
	  dfs(e[i].v);
	  if (mx[e[i].v]+e[i].d>mx[v])
	  {
	    smx[v]=mx[v],smxp[v]=mxp[v];
		mx[v]=mx[e[i].v]+e[i].d,mxp[v]=mxp[e[i].v];
	  }
	  else if (mx[e[i].v]+e[i].d>smx[v])
	  {
	    smx[v]=mx[e[i].v]+e[i].d,smxp[v]=mxp[e[i].v];
	  }
	}
}

int lca(int x,int y)
{
  if (dep[x]<dep[y]) swap(x,y);
  for(int i=19;i>=0;i--)
  {
    if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
  }
  if (x==y) return x;
  for(int i=19;i>=0;i--)
  {
    if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
  }
  return fa[x][0];
}

ll calc_dis(int x,int y)
{
  int f=lca(x,y);
  return dd[x]+dd[y]-2*dd[f];
}

int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1,a,b,d;i<n;i++)
  {
    scanf("%d%d%d",&a,&b,&d);
    insert(a,b,d),insert(b,a,d);
  }
  
  dep[1]=dd[1]=0;
  dfs(1);
  mx[0]=smx[0]=0;
  int mxi=0;
  for(int i=1;i<=n;i++)
    if (mx[i]+smx[i]>mx[mxi]+smx[mxi]) mxi=i;
  
  for(int i=1;i<=19;i++)
    for(int j=1;j<=n;j++)
	  fa[j][i]=fa[fa[j][i-1]][i-1];
  dis=calc_dis(mxp[mxi],smxp[mxi]);
  
  ans=0;
  for(int i=1;i<=n;i++)
    if (i!=mxp[mxi]&&i!=smxp[mxi])
	  ans=max(ans,min(calc_dis(mxp[mxi],i),calc_dis(smxp[mxi],i)));
  
  printf("%lld",dis+ans);
  return 0;
}


### 计算从1到N的阶乘之和 在编程竞赛中,计算从1到N的阶乘之和是一个常见的题目。以下是实现该功能的方法以及一些注意事项。 #### 方法描述 可以通过循环来逐步累加每个数的阶乘值。由于阶乘增长非常迅速,因此需要注意数据类型的选取以防止溢出。通常情况下,在C++中可以选择`unsigned long long`作为存储结果的数据类型[^3]。 下面提供了一个基于C++的标准解决方案: ```cpp #include <iostream> using namespace std; int main() { unsigned int n; cout << "Enter a positive integer: "; cin >> n; unsigned long long sum = 0; // 存储最终的结果 unsigned long long factorial = 1; // 当前的阶乘值 for (unsigned int i = 1; i <= n; ++i) { factorial *= i; // 更新当前阶乘值 sum += factorial; // 累加至总和 } cout << "Sum of factorials from 1 to " << n << " is: " << sum << endl; return 0; } ``` 此代码片段展示了如何通过简单的迭代方法完成任务。注意这里使用了`unsigned long long`以适应较大的数值范围。 对于Python而言,其内置的大整数支持使得处理此类问题更加简便: ```python def factorial_sum(n): total, fact = 0, 1 for i in range(1, n + 1): fact *= i # Update the current factorial value. total += fact # Add it to the running total. return total n = int(input("Enter a number: ")) print(f"The sum of factorials up to {n} is:", factorial_sum(n)) ``` 上述Python版本无需担心数据类型的选择问题,因为Python自动管理大整数运算[^1]。 #### 性能优化建议 当面对更大的输入规模时,应考虑算法的时间复杂度与空间复杂度。尽管本题目的直接解法已经足够高效,但在更复杂的场景下可能还需要引入动态规划或其他高级技巧[^2]。 另外,利用标准模板库(STL),如向量(vector)或者列表(list),可以帮助更好地管理和操作大量中间结果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值