luogu题解 P3629 【[APIO2010]巡逻】树的直径变式

  • 题目链接:

    https://www.luogu.org/problemnew/show/P3629

  • 分析

    最近被众多dalao暴虐,这道题傻逼地调了两天才知道错哪

    不过这题比较良心给你一个容易发现性质的图

    • 不修路时

      每条路走两次可知需要走\(2(N-1)\)

    • \(K=1\)

      送分给你,直接\(O(N)\)求直径,若直径长为\(L\),由于新加路还要走一步,少走了\(L-1\)

    • \(K=2\)

      如果还是用求直径的方法来求发现不太对,与原来直径重叠那部分又要多走一遍
      ,于是不妨把原来直径边权取反再求一边直径,若长为L',因为已经减去重叠部分还是少走\((L'-1)\)步,答案就为\(2(N-1)-L-L'\)

  • 注意

    好象直径取反后不能简单地用dfs求直径,因为在第一次找最远点时可能得到一个错误的答案,于是就用DP来求,顺便学了一下DP求直径

  • DP求树的直径

    \(D[v_i]\)表示在以\(v_i\)为根子树内走到的最大深度

    转移:\(v_1,v_2...v_k\)\(v_i\)子树内节点 \(D[i]=max_{1<=j<=k}(D[v_j]+edge(v_i,v_j))\)

    \(v_a,v_b\)\(v_x\)子树内两节点,树的直径可以看作由四部分组成:

    \(D[v_a]+edge(v_a,v_x)+edge(v_x,v_b)+D[v_b]\)

    具体看代码实现

  • 代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <map>
#include <queue>
#include <algorithm>
#define ri register int 
#define ll long long
using namespace std;
const int maxn=100005;
const int inf=0x7fffffff;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c=='-';
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;
    return ;
}
int n,k;
struct Edge{
    int ne,to,dis;
}edge[maxn<<2];
int num_edge=-1,h[maxn];
int s,t;
inline void add_edge(int f,int t){
    edge[++num_edge].ne=h[f];
    edge[num_edge].to=t;
    edge[num_edge].dis=1;
    h[f]=num_edge;
}
int mx=-inf,vis[maxn];
void dfs_1(int fa,int cur,int cnt){
    for(ri i=h[cur];i!=-1;i=edge[i].ne){
        if(edge[i].to!=fa){
            dfs_1(cur,edge[i].to,cnt+1);    
        }
    }
    if(cnt>mx){
        mx=cnt,t=cur;
    }
    return ;
}
int pre[maxn],dmet[maxn],tot=0,ex=0;
void dfs_2(int fa,int cur,int cnt){
    for(ri i=h[cur];i!=-1;i=edge[i].ne){
        if(edge[i].to!=fa){
            dfs_2(cur,edge[i].to,cnt+1);
            pre[edge[i].to]=cur;
        }
    }
    if(cnt>mx){
        mx=cnt,s=cur;
    }
    return ;
}
int diameter;
void dfs_3(int fa,int cur,int cnt){
    if(cur==t){
        diameter=cnt;
        return ;
    }
    for(ri i=h[cur];i!=-1;i=edge[i].ne){
        int v=edge[i].to;
        if(vis[v]&&v!=fa){
            dfs_3(cur,v,cnt+1);
            edge[i].dis=-1;
            edge[i^1].dis=-1;
        }
    }   
    return ;
}
int d[maxn];
void dp(int fa,int now){
    for(ri i=h[now];i!=-1;i=edge[i].ne){
        int v=edge[i].to;
        if(v==fa)continue;
        dp(now,v);
        mx=max(mx,d[now]+d[v]+edge[i].dis);//上一次循环已更新一次d[now]
        d[now]=max(d[now],d[v]+edge[i].dis);
    }
    return  ;
}
int main(){
    int x,y;
    read(n),read(k);
    memset(h,-1,sizeof(h));
    for(ri i=1;i<n;i++){
        read(x),read(y);
        add_edge(x,y);
        add_edge(y,x);
    }
    memset(vis,0,sizeof(vis));
    dfs_1(0,1,0);
    mx=-inf;    
    dfs_2(0,t,0);
    int tmp=s;
    while(tmp!=t){
       vis[tmp]=1;
       dmet[++tot]=tmp;
       tmp=pre[tmp];
    }
    dmet[++tot]=t,vis[t]=1;
    dfs_3(0,s,0);
    //cout<<s<<' '<<t<<' '<<diameter<<endl;
    /*-------*/
    if(k==2){
    mx=0;
    dp(0,1);
    int diameter_2=mx;
    //cout<<mx<<endl;
    if(mx<0)diameter_2=0;
    printf("%d\n",2*n-diameter-diameter_2);
    }
    else{
        printf("%d\n",2*(n-1)-(diameter-1));
    }
    return 0;
} 

转载于:https://www.cnblogs.com/Rye-Catcher/p/9254705.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值