树中最长路径 树形DP HDU 3534 Tree

http://acm.hdu.edu.cn/showproblem.php?pid=3534

题意:给出一颗树(n个顶点,n-1条边)

求最长的路径,以及最长路径的条数。

路径无非就是连接两个点直接的路。

因为是一颗树,所以连接两个点肯定是唯一的路径。

 

其实就是求两点间距离的最大值,以及这个最大值有多少个。

 

很裸的树形DP;

首先统计出结点到叶子结点的最长距离和次长距离。

 

然后找寻经过这个点的,在这个为根结点的子树中的最长路径个数目。

#include 
#include 
#include 
#include 
using namespace std;
const int MAXN=100010;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,len;
}edge[MAXN*2];
int head[MAXN];
int tol;
int maxn[MAXN];//该节点往下到叶子结点的最大距离
int smaxn[MAXN];// 次大距离
int maxn_num[MAXN];//最大距离的个数
int smaxn_num[MAXN];//次大距离的个数
int path[MAXN];//该结点为根的子树中,包含该结点的最长路径长度
int num[MAXN];//最长路径的长度

void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}
void add(int u,int v,int w)
{
    edge[tol].to=v;
    edge[tol].len=w;
    edge[tol].next=head[u];
    head[u]=tol++;
    edge[tol].to=u;
    edge[tol].len=w;
    edge[tol].next=head[v];
    head[v]=tol++;
}

void dfs(int u,int pre)
{
    maxn[u]=smaxn[u]=0;
    maxn_num[u]=smaxn_num[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==pre)continue;
        dfs(v,u);
        if(maxn[v]+edge[i].len>maxn[u])
        {
            smaxn[u]=maxn[u];
            smaxn_num[u]=maxn_num[u];
            maxn[u]=maxn[v]+edge[i].len;
            maxn_num[u]=maxn_num[v];
        }
        else if(maxn[v]+edge[i].len==maxn[u])
        {
            maxn_num[u]+=maxn_num[v];
        }
        else if(maxn[v]+edge[i].len>smaxn[u])
        {
            smaxn[u]=maxn[v]+edge[i].len;
            smaxn_num[u]=maxn_num[v];
        }
        else if(maxn[v]+edge[i].len==smaxn[u])
        {
            smaxn_num[u]+=maxn_num[v];
        }
    }
    if(maxn_num[u]==0)//叶子结点
    {
        maxn[u]=smaxn[u]=0;
        maxn_num[u]=smaxn_num[u]=1;
        path[u]=0;
        num[u]=1;
        return;
    }
    int c1=0,c2=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==pre)continue;
        if(maxn[u]==maxn[v]+edge[i].len)c1++;
        else if(smaxn[u]==maxn[v]+edge[i].len)c2++;
    }
    path[u]=0;
    num[u]=0;
    if(c1>=2)//最长+最长
    {
        int tmp=0;
        path[u]=maxn[u]*2;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v==pre)continue;
            if(maxn[u]==maxn[v]+edge[i].len)
            {
                num[u]+=tmp*maxn_num[v];
                tmp+=maxn_num[v];
            }
        }
    }
    else if(c1>=1 && c2>=1)//最长+次长
    {
        path[u]=maxn[u]+smaxn[u];
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v==pre)continue;
            if(maxn[u]==maxn[v]+edge[i].len)
            {
                num[u]+=maxn_num[v]*smaxn_num[u];
            }
        }
    }
    else//最长
    {
        path[u]=maxn[u];
        num[u]=maxn_num[u];
    }
}
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        int u,v,w;
        init();
        for(int i=1;ians1)
            {
                ans1=path[i];
                ans2=num[i];
            }
            else if(path[i]==ans1)
                ans2+=num[i];
        }
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}
问树的直径及直径的个数。
对每个点来说,经过它的最长路径有3种构成
1° 只有一个儿子,而且到父亲的距离短
2° 有多个儿子,其中的两个儿子与它相连
3° 一个儿子连上它和父亲
对前两种,就是max[now],但是对第三种,如果算为max[now]的话,那么还会在max[father]处算一遍,所以max[now]中不包含父亲上的信息。
然后现在cnt[now]有三种情况
1°最长边只有一个,次长边没有,cnt[now] = 1
2°最长边只有一个,次长边有,cnt[now] = 次长边的个数
3°最长边有多个,cnt[now] = blablabla……(太长了,,,看代码吧)
由于不需要考虑父亲,所以一次dfs解决
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int tail[10010], to[10010<<1], pre[10010<<1], cost[10010<<1], tot;

inline void add(int _from, int _to, int _cost) {
    pre[tot] = tail[_from];
    to[tot] = _to;
    cost[tot] = _cost;
    tail[_from] = tot++;
}

int first[10010], first_cnt[10010], second[10010], second_cnt[10010];
int dia[10010], cnt[10010];

void dfs(int now, int fa) {
    first[now] = first_cnt[now] = second[now] = second_cnt[now] = 0;
    dia[now] = cnt[now] = 0;
    for(int i = tail[now]; i != -1; i = pre[i]) {
        int _to = to[i], _cost = cost[i];
        if(_to == fa)
            continue;
        dfs(_to, now);
        if(first[now] < first[_to] + _cost) {
            second[now] = first[now];
            second_cnt[now] = first_cnt[now];
            first[now] = first[_to] + _cost;
            first_cnt[now] = first_cnt[_to];
            if(!second_cnt[now])
                dia[now] = first[now], cnt[now] = first_cnt[now];
            else 
                dia[now] = first[now] + second[now], cnt[now] = first_cnt[now]*second_cnt[now];
        } else if(first[now] == first[_to] + _cost) {
            if((dia[now]>>1) == first[now])
                cnt[now] += first_cnt[now] * first_cnt[_to];
            else
                cnt[now] = first_cnt[now] * first_cnt[_to], dia[now] = first[now]<<1;
            first_cnt[now] += first_cnt[_to];
        } else if(second[now] < first[_to] + _cost) {
            second[now] = first[_to] + _cost;
            second_cnt[now] = first_cnt[_to];
            if((dia[now]>>1) != first[now])
                dia[now] = first[now] + second[now], cnt[now] = first_cnt[now]*second_cnt[now];
        } else if(second[now] == first[_to] + _cost) {
            second_cnt[now] += first_cnt[_to];
            if((dia[now]>>1) != first[now])
                cnt[now] = first_cnt[now]*second_cnt[now];
        }
    }
    if(!first_cnt[now])
        first_cnt[now] = cnt[now] = 1;
}

int main() {
    int n;
    while(scanf("%d", &n) > 0) {
        tot = 0;
        memset(tail, -1, sizeof tail);
        for(int i = 1; i < n; i++) {
            int _u, _v, _cost;
            scanf("%d%d%d", &_u, &_v, &_cost);
            add(_u, _v, _cost);
            add(_v, _u, _cost);
        }
        dfs(1, -1);
        int maxx = 0, ans = 0;
        for(int i = 1; i <= n; i++) {
            if(maxx < dia[i])
                maxx = dia[i], ans = cnt[i];
            else if(maxx == dia[i])
                ans += cnt[i];
        }
        printf("%d %d\n", maxx, ans);
    }
    return 0;
}

情况1:由某叶子节点出发产生的最长路径直接构成

情况2:由某有多个儿子的节点出发产生的两条长路径组成,这其中,又可以分为两条长路径长度相等与否两种情况

所以 在dp的时候,我们需要记录每个节点出发产生的最长路径和次长路径,以及他们的数量,数量的统计也是非常麻烦

详细请见代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 998244353
#define MAX 100000000
using namespace std;
int t,n,m,p,k,tt,f;
int x;

int head[10010];
typedef struct Node
{
    int en;
    int value;
    int next;
}node;
node edge[20010];
typedef struct DPnode
{
    int dp1,dp2,len,nn;
    int n1,n2;
}DP;
DP dp[10010];
void ini()
{
    int x,y,z;
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d%d",&x,&y,&k);
        edge[2*i-1].en=y;
        edge[2*i-1].next=head[x];
        edge[2*i-1].value=k;
        head[x]=2*i-1;
        edge[2*i].en=x;
        edge[2*i].next=head[y];
        edge[2*i].value=k;
        head[y]=2*i;
    }
}
void dfs(int s,int p)
{
    dp[s].dp1=dp[s].dp2=dp[s].len=dp[s].n1=dp[s].n2=dp[s].nn=0;
    int leaf=1;
    for(int i=head[s];i;i=edge[i].next)
    {
        int q=edge[i].en;
        if(q==p)
            continue;
        leaf=0;
        dfs(q,s);
        int tmp=dp[q].dp1+edge[i].value;
        if(tmp>dp[s].dp1)
        {
            dp[s].dp2=dp[s].dp1;
            dp[s].n2=dp[s].n1;
            dp[s].dp1=tmp;
            dp[s].n1=dp[q].n1;
        }
        else if(tmp==dp[s].dp1)
        {
            dp[s].n1+=dp[q].n1;
        }
        else if(tmp>dp[s].dp2)
        {
            dp[s].dp2=tmp;
            dp[s].n2=dp[q].n1;
        }
        else if(tmp==dp[s].dp2)
        {
            dp[s].n2+=dp[q].n1;
        }
    }
    if(leaf)
    {
        dp[s].n1=1;dp[s].nn=1;
        dp[s].len=0;
        dp[s].dp1=0;
        return;
    }
    int c1=0,c2=0;
    for(int i=head[s];i;i=edge[i].next)
    {
        int q=edge[i].en;
        if(q==p)
            continue;
        int tmp=dp[q].dp1+edge[i].value;
        if(tmp==dp[s].dp1)
            c1++;
        else if(tmp==dp[s].dp2&&dp[s].dp2)
            c2++;
    }
    if(c1>1)
    {
        dp[s].len=dp[s].dp1*2;
        int sum=0;
        for(int i=head[s];i;i=edge[i].next)
        {
            int q=edge[i].en;
            if(q==p)
                continue;
            if(dp[q].dp1+edge[i].value==dp[s].dp1)
            {
                dp[s].nn+=sum*dp[q].n1;
                sum+=dp[q].n1;
            }
        }
    }
    else if(c2>0)
    {
        dp[s].len=dp[s].dp1+dp[s].dp2;
        for(int i=head[s];i;i=edge[i].next)
        {
            int q=edge[i].en;
            if(q==p)
                continue;
            if(dp[q].dp1+edge[i].value==dp[s].dp2)
            {
                dp[s].nn+=dp[s].n1*dp[q].n1;
            }
        }
    }
    else
    {
        dp[s].len=dp[s].dp1;
        dp[s].nn=dp[s].n1;
    }
    return ;
}
void solve()
{
    int ans=0;
    int num=0;
    for(int i=1;i<=n;i++)
    {
        if(dp[i].len>ans)
        {
            ans=dp[i].len;
            num=dp[i].nn;
        }
        else if(dp[i].len==ans)
        {
            num+=dp[i].nn;
        }
    }
    printf("%d %d\n",ans,num);
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(head,0,sizeof(head));
        ini();
        dfs(1,0);
        solve();
    }
    return 0;
}
思路:最长边以前做过,用树形DP可以。求最长边的时候可以统计一下最长边的个数,那么以u为根节点的子树中最长边为:Len[u]+max(len[v]),个数node[u]*node[v]。

#include
#include
const int N=500000;
const int inf=0x3fffffff;
int head[N],num,Len[N],ans,node[N],k;
struct edge
{
	int ed,w,next;
}e[N*2];
void addedge(int x,int y,int w)
{
	e[num].ed=y;e[num].w=w;e[num].next=head[x];head[x]=num++;
	e[num].ed=x;e[num].w=w;e[num].next=head[y];head[y]=num++;
}
void dfs(int u,int fa)
{
	int i,v,temp;
	Len[u]=0;//最长边
	node[u]=1;//最长边的个数
	for(i=head[u];i!=-1;i=e[i].next)
	{
		v=e[i].ed;
		if(v==fa)continue;
		dfs(v,u);
		temp=Len[v]+e[i].w;
		if(temp+Len[u]>ans)//最长边经过v
		{
			k=node[v]*node[u];
			ans=temp+Len[u];
		}
		else if(temp+Len[u]==ans)
			k+=node[v]*node[u];
		if(Len[u]


你可能感兴趣的:(树形DP)