两个吃奶一个添下面视频_人妻第一页香蕉网_欧美xxxx少妇_妺妺窝人体色www婷婷

一、什么是樹型動態(tài)規(guī)劃 

顧名思義,樹型動態(tài)規(guī)劃就是在“樹”的數(shù)據(jù)結(jié)構(gòu)上的動態(tài)規(guī)劃,平時作的動態(tài)規(guī)劃都是線性的或者是建立在圖上的,線性的動態(tài)規(guī)劃有二種方向既向前和向后,相應(yīng)的線性的動態(tài)規(guī)劃有二種方法既順推與逆推,而樹型動態(tài)規(guī)劃是建立在樹上的,所以也相應(yīng)的有二個方向: 

(1)葉->根:在回溯的時候從葉子節(jié)點往上更新信息

(2)根 - >葉:往往是在從葉往根dfs一遍之后(相當(dāng)于預(yù)處理),再重新往下獲取最后的答案。

不管是 從葉->根 還是 從 根 - >葉,兩者都是根據(jù)需要采用,沒有好壞高低之分。

樹形DP的難點有什么?

(1)和線性動態(tài)規(guī)劃相比,樹形DP往往是要利用遞歸的,所以對遞歸不熟悉的同學(xué),是一道小小的障礙,說是小小的,因為要去理解也不難。

(2)細節(jié)多,較為復(fù)雜的樹形DP,從子樹,從父親,從兄弟……已經(jīng)一些小的要處理的地方,腦子不清晰的時候做起來頗為惡心

(3)狀態(tài)表示和轉(zhuǎn)移方程,也是真正難的地方。做到后面,樹形DP的老套路都也就那么多,難的還是怎么能想出轉(zhuǎn)移方程,狀壓DP、概率DP這些特別的DP應(yīng)該說做到最后都是這樣!

建有向圖還是無向圖? 一般來說我們做樹DP都是從上往下遞歸,如果不亂搞是不會從子節(jié)點往根節(jié)點的遍歷的,這時候可以建有向圖,只加邊一次就可以,對于有“思想潔癖”的人來說,如果加一次邊就可以再讓他加兩次是很難受的。但是有些題目可能很隱蔽的某個地方涉及到從子節(jié)點往上訪問,就會錯的很慘。所以,一般不非常確定建有向圖就可的話還是建無向圖吧。

做樹形dp常用的兩種方法:

(1)由子節(jié)點來更新父節(jié)點;

(2)由父節(jié)點來更新子節(jié)點。


二、例題解析

例題1:樹的直徑 (樹中最遠兩點的距離):

思路:對每個點來說,求出以它為父節(jié)點,路徑的最大值和次大值

代碼:

#include<bits/stdc++.h>
 
using namespace std;
 
const int N=2e4+10;
 
int e[N],ne[N],w[N],h[N],idx;
int f1[N],f2[N];//f1[i]表示父節(jié)點為i的最遠距離,f2[i]表示次遠距離
 
void add(int a,int b,int c)
{
    e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
 
void dfs(int u,int fa)
{
    //f1[u]=0,f2[u]=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs(j,u);
        int d=f1[j]+w[i];
        if(d>f1[u]){
            f2[u]=f1[u];
            f1[u]=d;
        }
        else if(d>f2[u]){
            f2[u]=d;
        }
    }
}
signed main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs(1,-1);
    
    int ans=-1e9;
    for(int i=1;i<=n;i++){
        ans=max(ans,f1[i]+f2[i]);
    }
    cout<<ans<<endl;
    return 0;
}

例題2:樹的中心(以某個點為中心,其到其他點的最大值最小,則該點為中心)

思路:我們可以像例1一樣,例1是以1為根,來找最長路徑,我們可以把每個點都當(dāng)作一次根,都做一次遍歷,來求最長路徑,然后取min即是答案,但這樣會超時,因此考慮換個思路。

(1)對每個節(jié)點,其到其他點最大值為max(向下到其兒子最遠點,向上的最遠點),這里前一個很好求,即例1中的f1[i], 而對第二個要進行討論。

(2)對于向上的最遠點有兩種情況:向上走就是求一個點的父節(jié)點的不走該節(jié)點的最長路徑,其實我們知道了每一個節(jié)點向下走的長度就可以知道向上的最長路徑了,一個子節(jié)點 j 的向上最長路徑就是 它的父節(jié)點 u 的最長向上路徑和最長向下路徑取最大值,如果向下最長路徑經(jīng)過了 j 就改為第二長的向下路徑,對應(yīng)代碼:

if(p1[u]==j)up[j]=max(up[u],d2[u])+w[i];
else up[j]=max(up[u],d1[u])+w[i];

此做兩次樹形DP即可

#include<bits/stdc++.h>
 
using namespace std;
 
const int N=2e4+10;
 
int e[N],ne[N],h[N],w[N],idx;
 
int f1[N],p1[N],f2[N];//p[i]記錄最長路徑是由哪個子節(jié)點轉(zhuǎn)移過來的
int up[N];//向上走的最大值
 
void add(int a,int b,int c)
{
    e[idx]=b; ne[idx]=h[a] ; w[idx]=c ; h[a]=idx++;
}
 
void dfs1(int u,int fa)
{
    f1[u]=f2[u]=0;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dfs1(j,u);
        int d=f1[j]+w[i];
        if(d>f1[u]){
            f2[u]=f1[u];
            f1[u]=d;
            p1[u]=j;//記錄最長路徑由哪個兒子轉(zhuǎn)移來的
        }
        else if(d>f2[u]) f2[u]=d;
    }
}
 
void dfs2(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        if(p1[u]==j)//說明向上走的最大路徑經(jīng)過該兒子節(jié)點
        {
            up[j]=max(up[u],f2[u])+w[i];
        }
        else up[j]=max(up[u],f1[u])+w[i];
 
        dfs2(j,u);//注意這里是由父節(jié)點更新子節(jié)點
                  //因此要先更新信息在遞歸處理,與dfs1不同
    }
}
signed main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);
 
    for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs1(1,-1);//由子節(jié)點更新父節(jié)點
    dfs2(1,-1);//由父節(jié)點更新子節(jié)點
    int ans=1e9;
    for(int i=1;i<=n;i++){
        ans=min(ans,max(f1[i],up[i]));
    }
    cout<<ans<<endl;
    return 0;
}


點贊(0)

C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點擊了解:

一點編程也不會寫的:零基礎(chǔ)C語言學(xué)練課程

解決困擾你多年的C語言疑難雜癥特性的C語言進階課程

從零到寫出一個爬蟲的Python編程課程

只會語法寫不出代碼?手把手帶你寫100個編程真題的編程百練課程

信息學(xué)奧賽或C++選手的 必學(xué)C++課程

藍橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程

手把手講解近五年真題的藍橋杯輔導(dǎo)課程

Dotcpp在線編譯      (登錄可減少運行等待時間)