【FJOI2014[bzoj4016]】最短路径树问题

本博客介绍了如何解决一个关于无向图的最短路径树问题,寻找包含k个点的最长简单路径及其方案数。通过点分治和树形动态规划的方法,首先构建字典序最小的最短路径树,再利用BFS处理子树信息,最终更新答案和方案数。文章强调了在实现过程中的一些关键细节,如边的排序、树的构建、子树大小的调整以及动态规划数组的初始化等。

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

  • link to problem
  • 【题目大意】
    给出一张无向图,求图中字典序最小的最短路径树上最长的包含 k 个点的简单路径长度及方案数。其中最短路径树定义:从顶点 1 出发,往其余所有点分别走一次并返回。往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径。到达该点后按原路返回,然后往其他点走,直到所有点都走过。

  • 【题解】点分治+树形dp
    首先处理字典序最小的最短路径树。显然知道树上节点到顶点 1 距离最短,为了保证字典序将边排序,做个 spfa 求最短路(或者 dijkstra+heap 优化),再一遍 dfs 建树。
    接下来,依然按照点分治的基本思路将路径分为是否经过当前树根节点两类。
    考虑经过根节点情况。我们记 x 为根节点(重心):

    • f [i]:x为根节点树上已处理的节点到根 x 经过节点数为 i 的路径最大长度;
    • g[i]:f[i] 意义下对应的方案数;
    • f f [i]:当前处理的根为 son 的子树上节点到 son 经过节点数为 i 的路径最大长度;
    • gg[i]:ff[i] 意义下对应的方案数。//这名字是不是预示了我要gg的结果。。

    每一次我们先 bfs 处理 ff 和 gg数组。再用当前子树信息和之前存下的信息更新答案和方案数。做完以后将子树信息和其他信息合并。


  • 【细节注意】//具体看程序
    • 字典序最小:我处理的时候是把它倒序排这样存边倒着做回来就能保证从序号小开始做。。。然而打崩了好几次都是建树的问题QAQAQAQ….
    • 在做 dp 的过程中在从当前树找子树重心的时候,注意到由于之前乱搞可能当前子树 son 大小会比size[x]大???(什么鬼),所以如果不对就扣一下好了。。。
    • 数组清空:这个我真不知道什么鬼,越界访问这种神奇的东西。反正我是在做完了spfa之后dp数组都莫名有了初始值,然后清空一下竟然就对了。辣鸡题目还我AC率TAT… 不过还有要注意 dp 数组的清空就是了。

{ 总而言之树分治的细节总是很多,打和调试分不同过程想清楚就好啦 (⊙v⊙) }


【呆马 ( ⊙o⊙ )】

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#define inf 1000000000
#define maxn 30005
struct edge{ int to,s,nxt;}e[maxn*4],e1[maxn*4];
struct edge1{ int u,v,w;}a[maxn*4];
int n,m,k,ans,sum,cnt,mx,tot,rt,fi[maxn],fi1[maxn],s[maxn],d[maxn],num[maxn],
    p[maxn],q[maxn*8],f[maxn],g[maxn],ff[maxn],gg[maxn];
bool bo[maxn];
    void add1(int u,int v,int w)
    {
        e1[++cnt].to=v;e1[cnt].s=w;
        e1[cnt].nxt=fi1[u];fi1[u]=cnt;
    }
    void add(int
### 关于洛谷平台上线段树练习题目的推荐 #### 题目一:P3810 【模板】线段树 1 此题目作为入门级的线段树构建与基本操作实现,适合初学者掌握线段树的基础概念和简单应用。通过这道题可以熟悉如何在线段树上执行单点更新以及区间求和的操作[^2]。 ```cpp #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 5; struct SegmentTree { int sum[maxn << 2]; void pushUp(int rt) {sum[rt] = sum[rt<<1] + sum[rt<<1|1];} void build(int l, int r, int rt){ if(l == r){ scanf("%d", &sum[rt]); return ; } int m = (l+r)>>1; build(lson); build(rson); pushUp(rt); } // Other functions like update and query can be implemented here. }; int main(){ SegmentTree tree; int n,m,opt,x,y,k; char str[2]; while(scanf("%d%d",&n,&m)!=EOF){ memset(tree.sum,0,sizeof(tree.sum)); tree.build(1,n,1); for(int i=0;i<m;++i){ getchar(); gets(str); sscanf(str,"%d %d %d",&opt,&x,&y); switch(opt){ case 1 : k=y-tree.a[x]; tree.update(x,k,1,n,1);break; case 2 : printf("%lld\n",tree.query(x,y,1,n,1)); break; } } } return 0; } ``` #### 题目二:P2791 幼儿园篮球题 该问题不仅考察了对线段树的理解程度,还涉及到更复杂的逻辑思考能力。在这个场景下,需要利用线段树来高效解决有关子树内节点权重总和的问题,在实际编程竞赛中具有较高的实用性价值[^1]。 #### 题目三:P4566 [FJOI2018]新型城市化 这是一个较为高级的应用实例,涉及动态开点线段树的知识点。对于想要深入研究数据结构优化技巧的学习者来说是一个很好的挑战对象。这类题目有助于提高解决问题的能力,并加深对复杂算法设计模式的认识。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值