bzoj 3246: [Ioi2013]Dreaming 树形dp+树的直径

题意

你有一个n个点m条边的森林,编号从0开始,边有边权,你现在要添加若干边权为L的边,满足:
1、最后n个点构成一颗树。
2、这棵树的直径尽量小。
请你求出这个最小的直径是多少。
n<=500000

分析

设d[i]表示i到其所在子树的最远的点的距离。显然每一棵树与其他树相连的必然都是同一个点,且必然是d[i]最小的那个点。然后把每棵树缩成一个点,点权为其最小的d[i]。那么最后一定是构成一个菊花图,且中间那个点必然是点权最大的那个点。
那么我们只要预处理处d[i],建好图后用两次dfs来求直径即可。

比赛的时候想到了第一个结论,但没想到第二个结论,所以比赛的时候我的做法是动态维护d[i]最小的点:显然d[i]最小的点必然为树的直径的中点,那么只要启发式合并的同时维护直径,再用倍增大法找到其中点即可。
由于我的方法要求lca,不仅多一个log而且还被卡了一波空间2333

代码

#include
#include
#include
#include
#include
#define N 500005
using namespace std;

int n,m,cnt,last[N],mx1[N],mx2[N],num1[N],pts[N],ans,num,L;
struct edge{int to,next,len;}e[N*2];
bool vis[N];

void addedge(int u,int v,int len)
{
    e[++cnt].to=v;e[cnt].len=len;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].len=len;e[cnt].next=last[v];last[v]=cnt;
}

void dp1(int x)
{
    vis[x]=1;
    for (int i=last[x];i;i=e[i].next)
    {
        if (vis[e[i].to]) continue;
        dp1(e[i].to);
        int w=mx1[e[i].to]+e[i].len;
        if (w>mx1[x]) mx2[x]=mx1[x],mx1[x]=w,num1[x]=e[i].to;
        else if (w>mx2[x]) mx2[x]=w;
    }
}

void dp2(int x,int root)
{
    vis[x]=1;
    if (mx1[x]for (int i=last[x];i;i=e[i].next)
    {
        if (vis[e[i].to]) continue;
        int w=0;
        if (e[i].to==num1[x]) w=mx2[x]+e[i].len;
        else w=mx1[x]+e[i].len;
        if (w>mx1[e[i].to]) mx2[e[i].to]=mx1[e[i].to],mx1[e[i].to]=w,num1[e[i].to]=x;
        else if (w>mx2[e[i].to]) mx2[e[i].to]=w;
        dp2(e[i].to,root);
    }
}

void dfs(int x,int fa,int len)
{
    if (len>ans) ans=len,num=x;
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa) dfs(e[i].to,x,len+e[i].len);
}

int main()
{
    scanf("%d%d%d",&n,&m,&L);
    for (int i=1;i<=m;i++)
    {
        int x,y,len;
        scanf("%d%d%d",&x,&y,&len);
        addedge(++x,++y,len);
    }
    for (int i=1;i<=n;i++)
        if (!vis[i]) dp1(i);
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=n;i++)
        if (!vis[i]) dp2(i,i);
    int x=0;
    for (int i=1;i<=n;i++)
        if (pts[i]&&(mx1[pts[i]]>mx1[x]||!x)) x=pts[i];
    for (int i=1;i<=n;i++)
        if (pts[i]&&pts[i]!=x) addedge(x,pts[i],L);
    dfs(1,0,0);
    x=num;ans=num=0;
    dfs(x,0,0);
    printf("%d",ans);
    return 0;
}

你可能感兴趣的:(树形dp,树的直径&重心)