阿Q的棒棒糖

阿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;
}

posted @   Chano_sb  阅读(138)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示