阿Q的棒棒糖
https://hszxoj.com/images/211012_AxTB6T3Ym2.png
题意很简单:给定一棵树,删掉一条边之后得到两棵树,求两棵树的最大直径和
朴素算法:枚举要删去的边,分别以边的起点和终点为起点跑dfs,处理出树上最长链和次长链,直径为最长链和次长链的加和;
大概就是这么个意思
int zuichanglian,cichanglian;
void dfs(int st,int fa){
for(int i=head[st];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
cichanglian=zuichanglian;
zuichanglian+=e[i].dis;
dfs(v,u);
}
}
int main(){
建图;
for(int i=1;i<=m;++i){
int x=e[i].from,y=e[i].to;
dfs(x,y);//y=fa是为了搜索时不会搜到以y为根的子树
D1=zuichanglian+cichanglian;
dfs(y,x);
D2=zuichanglian+cichanglian;
ans=max(ans,D1+D2);
}
}
正解为树形dp
Code(from Eafoo)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
using namespace std;
#define AKNOI printf("I AK NOI AND JOIN IN TSINGHUA UNIVERSITY!\n")
const int maxn = 100010, maxe = 200010;
int read()
{
int x = 0;
char c;
bool f = 0;
while (!isdigit(c = getchar())) {
if (c == '-') {
f = 1;
}
}
do {
x = (x << 1) + (x << 3) + (c ^ 48);
} while (isdigit(c = getchar()));
if (f) {
return -x;
}
return x;
}
int n;
int head[maxn], len;
int to[maxe], nxt[maxe], dis[maxe];
void Ins(int u, int v, int d)
{
to[++len] = v;
nxt[len] = head[u];
dis[len] = d;
head[u] = len;
}
int maxD[maxn], maxNode[maxn][4][3], maxSon[maxn][3][3], ans, cnt;
/*
maxD[x]: x子树的最大直径
maxNode[x][1][1]: x子树中的最远点到x距离
maxNode[x][1][2]: x子树中的最远点的编号
maxNode[x][2][1]: x子树中次远点到x距离
maxNode[x][2][2]: x子树中次远点的编号
maxNode[x][3][1]: x子树中次次远点到x距离
maxNode[x][3][2]: x子树中次次远点的编号
maxSon[x][1][1]: x子树中直径最大的子树的直径
maxSon[x][1][2]: x子树中直径最大的子树的根节点编号
maxSon[x][2][1]: x子树中直径次大的子树的直径
maxSon[x][2][2]: x子树中直径次大的子树的根节点编号
*/
void dfs(int u, int fa)
{
//预处理
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
if (v == fa) {
continue;
}
dfs(v, u);
//求出x子树中直径最大的两颗子树
if (maxD[v] > maxSon[u][1][1]) {
memcpy(maxSon[u][2], maxSon[u][1], sizeof maxSon[u][1]);
maxSon[u][1][1] = maxD[v];
maxSon[u][1][2] = v;
} else if (maxD[v] > maxSon[u][2][1]) {
maxSon[u][2][1] = maxD[v];
maxSon[u][2][2] = v;
}
//求出x到x子树中最远点、次远点、次次远点
int z = maxNode[v][1][1] + dis[i];
if (z > maxNode[u][1][1]) {
memcpy(maxNode[u][3], maxNode[u][2], sizeof maxNode[u][2]);
memcpy(maxNode[u][2], maxNode[u][1], sizeof maxNode[u][1]);
maxNode[u][1][1] = z;
maxNode[u][1][2] = v;
} else if (z > maxNode[u][2][1]) {
memcpy(maxNode[u][3], maxNode[u][2], sizeof maxNode[u][2]);
maxNode[u][2][1] = z;
maxNode[u][2][2] = v;
} else if (z > maxNode[u][3][1]) {
maxNode[u][3][1] = z;
maxNode[u][3][2] = v;
}
//求x子树的最大直径
maxD[u] = max(maxSon[u][1][1], maxNode[u][1][1] + maxNode[u][2][1]);
}
}
void dfs2(int u, int fa, int cm, int l)
{
if (u != 1) {
ans = max(ans, maxD[u] + cm);
}
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
if (v == fa) {
continue;
}
/*
删去一条边后,直径有以下情况:
1.在u的子树里
2.u所属连通块的直径
3.u子树没被删去的最远点与u上方最远点的和
4.在u不同子树上最远的点的和
*/
int a1, a2, a3;
if (maxNode[u][1][2] == v) {
a1 = maxNode[u][2][1];
a2 = maxNode[u][2][1] + maxNode[u][3][1];
} else if (maxNode[u][2][2] == v) {
a1 = maxNode[u][1][1];
a2 = maxNode[u][1][1] + maxNode[u][3][1];
} else {
a1 = maxNode[u][1][1];
a2 = maxNode[u][1][1] + maxNode[u][2][1];
}
if (maxSon[u][1][2] == v) {
a3 = maxSon[u][2][1];
} else {
a3 = maxSon[u][1][1];
}
dfs2(v, u, max(max(cm, a2), max(a3, a1 + l)), max(a1, l) + dis[i]);
}
}
int main()
{
freopen("lollipop.in", "r", stdin);
#ifndef DEBUG
freopen("lollipop.out", "w", stdout);
#endif
n = read();
for (int i = 1; i < n; ++i) {
int u = read(), v = read(), d = read();
Ins(u, v, d);
Ins(v, u, d);
}
dfs(1, 0);
ans = maxD[1];
dfs2(1, 0, 0, 0);
printf("%d\n", ans);
}
以下是Chen_jr的思路,然而由于在求解时容易被双层菊花图(样例就是一个小双层菊花图)hack,该思路的优秀之处在于如果原图是一个菊花图(所有的点都连在一个点上),由于在求解时砍掉叶子节点和根节点连边没有贡献就直接跳过,会节省很多的时间。
其实就是面向数据编程
算法描述:
和朴素算法相同,maxD[i]=maxline[i]+cichanglian;
先跑一遍dfs预处理每一棵树的直径和最长链,求解时枚举每一条边,求出以root为根节点的子树但不经过以v为根节点的子树的直径和最长链,更新答案
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=100000+5;
struct Edge{
int from,to,next,dis;
}e[maxn<<1];
int head[maxn],len,ans,n;
void Insert(int x,int y,int dis){
e[++len].to=y;
e[len].from=x;
e[len].dis=dis;
e[len].next=head[x];
head[x]=len;
}
int maxD[maxn],maxline[maxn];//直径 最长链
void update(int root,int fa){
maxD[root]=maxline[root]=0;
int sec=0;//root为根的子树上的次长链
for(int i=head[root];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
maxD[root]=max(maxD[root],maxD[v]);
int diss=maxline[v]+e[i].dis;
if(diss>maxline[root]){
sec=maxline[root];
maxline[root]=diss;
}
else sec=max(sec,diss);
}
maxD[root]=max(maxD[root],maxline[root]+sec);
}
void dfs(int root,int fa){
for(int i=head[root];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,root);
}
update(root,fa);//回溯时从叶子开始更新
}
void changeroot(int root,int fa){
int D=maxD[root],Line=maxline[root];
for(int i=head[root];i;i=e[i].next){
int v=e[i].to;
if(v==fa||!maxD[v])continue;//maxD[v]==0 v为叶子
update(root,v);
ans=max(ans,maxD[root]+maxD[v]);//以v为根节点的子树并没有被破坏,直径还是原来的直径
changeroot(v,root);
}
maxD[root]=D;maxline[root]=Line;
}
void work(){
scanf("%d",&n);
for(int i=1;i<=n-1;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
Insert(x,y,z);
Insert(y,x,z);
}
dfs(1,0);
changeroot(1,0);
printf("%d\n",ans);
}
int main(){
freopen("lollipop.in","r",stdin);
freopen("lollipop.out","w",stdout);
work();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了