樹形DP

DP有些求騙的東西,比如樹形DP。
怎麼說呢,樹也是個DAG,按理說,是DAG就能動規。



用題說事

先看一道題

洛谷P1271 聚會的快樂

得自己建樹,人名字符串還要映射……板噁心……

說正事。
這道題裏,上司—下屬關係就是一個天然的有向邊,利用它,從老闆開始,往下深搜就好啦~

Created with Raphaël 2.1.0開始建樹—————————————決策——————————————決策一:不取現在搜到的人決策二:取現在搜到的人,當他的上司不取才能進行結束

建樹當然是好用的鏈式前向星啦~不過轉二叉樹也可以
真正寫代碼時,是看取不取他的下屬
一個下屬取,一個下屬不取……
在加上本身取不取就是這一棵子樹最大值。

代碼 75分

再來一道題

洛谷P1271 有線電視網

分析下次上

代碼 AC

給你三個題

似乎不止三個……
洛谷 P2014 選課
洛谷 P2015 二叉蘋果樹
(紫書p282)
UVa 12186 Another Crisis
UVa 1220 Party at Hali-Bula
UVa 1218 Perfect Service
題解會有的……


幾個知識點(待補充)

來自紫書、藍書、挑戰程序設計競賽

樹的最大獨立集

對於一棵 n 個結點的無根樹,選出儘量多的結點,使其中任意兩個結點均不相鄰(即最大獨立集)。
洛谷P1271就是一個最大獨立集問題,不過帶了權(幽默係數)。然而,P1271是有根的(上司)。

那麼,對於最大獨立集問題,只需隨便“提溜”一個結點“當”根,然後就很開心了。
紫書p281
(紫書p281)
對結點i,只有兩種決策:選和不選。即方程:

d(i)=max{1+jgrandson(i)d(j),json(i)d(j)}

d(i)表示以 i 爲根結點的子樹的最大獨立集

寫代碼時,方法一是記搜(如P1271);方法二是“刷表法”:對結點 i ,計算出d(i)後,更新 i 的父親和祖父的累加值,這樣只需存每個結點的父親就好(只開一個數組)。

法一代碼

    int dp[N][2];
    int dfs(int i,int qu){
        if(dp[i][qu])return dp[i][qu];
        int ans=0;
        for(int e=head[i];e;e=nex[e]){
            int son=dfs(to[e],0);
            if(!qu)son=max(son,dfs(to[e],1));
            ans+=son;
            }
        return dp[i][qu]=ans+son*qu;
        }

法二代碼

樹的重心(質心)

紫書p281

對於一棵 n 個結點的無根樹(又是無根樹),找到一個點,使把樹變成以它爲根的有根樹時,最大子樹的節點數最小。也就是說,刪除它後最大的樹的節點數最少。

同樣,先“提溜”一個結點“當”根,設d(i)表示以 i 爲根的子樹中結點個數,則有:

d(i)=1+json(i)d(j)

然後隨便dfs就行了。
這只是把d(i)求出來了,刪除 i 時,在
f(i)=max{maxjson(i)d(j),nd(i)}

中,取一個最小的f(i),此時 i 就是重心。
紫書p281
(紫書p281)
f(i)表示刪除 i 後,最大子樹的結點數。n-d(i)表示 i 的父結點連的結點總數。因爲d(i)=1+json(i)d(j) ,d(i)就是圖中虛線方塊外的結點數,自然n-d(i)就是虛線方塊內的結點數。

代碼

int f[N],d[N];
int dfs(int i,int fa){
    int sum=0;
    for(int e=head[i];e;e=nex[e])
        if(to[e]!=fa)sum+=dfs(to[e],i);
    return d[i]=sum+1;
    }
int zhongxin(int root){
    int a=dfs(root,-1),ans=-1,ff=INF;
    for(int i=1;i<=n;i++){
        int son=n-d[i];
        for(int e=head[i];e;e=nex[e])
            son=max(son,d[to[e]]);
        if(son<ff){
            ff=son;
            ans=i;
            }
        }
    return ans;
    }

樹的直徑(樹的最長路徑、最遠點對)

紫書p281、挑戰程序設計競賽p295

對 n 個結點的無根樹,找到一條最長路徑,即找到兩個點,使它們距離最遠。

普通算法就是隨便提溜一個根root,然後dfs找到根最遠的點,再從這個點出發,dfs找最遠的點。這樣,第二次dfs找的路徑就是樹的直徑。
一眼看就是對的。可爲什麼對,不說了,不重要。代碼很簡單,不打了,挑戰程序設計競賽p295有。

重要的是DP算法。
紫書p281
(紫書p281)
dp只能求最長路長度,如果只要求長度,那就開心了。
設d(i)表示以 i 爲根的子樹中根到葉子的最大距離,則有:

d(i)=1+maxjson(i){d(j)}

其中”1”就是 i 到 j 的路徑長度,也可換成權值。然後就好理解了。
最後結果就是root的子節點中,d最大的兩個d(u),d(v),再加上2。(d(u)+d(i)+2)。
看上面的圖:d(u),d(v)已知,u到灰色節點距離爲1,v到灰色節點距離也爲1,加起來就是d(u)+d(i)+2(也可帶權)。
int d[N];
int dfs(int i,int fa){
    int ans=0;
    for(int e=head[u];e;e=nex[e])
        if(to[e]!=fa)
            ans+=dfs(to[e],i);
    return d[i]=ans+1;
    }

OBST(最優排序二叉樹)

藍書p63
有 n 個符號,每個符號有一個檢索頻率,互不相同。將它們建成一棵排序二叉樹,使總檢索次數(所有符號的頻率與其深度乘積之和,根的深度==1)最小。
UVa 10304 Optimal Binary Search Tree

這算序列型DP還是樹狀DP呢?不管,反正這個挺難的。
前方高能!!!
和XXX一樣,先選根,再遞歸左右子樹。
根的頻率知道了,那麼,它的兒子就要乘2,孫子就要乘3……太麻煩!直接所有的都加一次得了。子樹?子樹再當成根,遞歸。那方程就是:

d(i,j)=sum(i,j)+maxi<=k<=j{d(i,k1)+d(k+1,j)}

d(i,j)表示序列i~j的總檢索次數,sum(i,j)表示i~j頻率和。O(n3)
int sum[N],d[N][N];
int build(int l,int r){
    if(l==r)return sum[l]-sum[l-1];
    if(d[l][r])return d[l][r];
    int son=0;
    for(int k=l;k<=r;k++)
        son=max(son,build(l,k-1)+build(k+1,r));
    return son+(sum[r]-sum[l-1]);
    }
int work(){
    for(int i=1;i<=n;i++)
        sum[i]+=sum[i-1];//直接在輸入數組上做前綴和
    return build(1,n);
    }

這只是比較樸素的方法……
前方真正高能!!!
能降至O(n2) 。記K(i,j)爲讓d(i,j)取最小值的決策k,有

K(i,j)K(i,j+1)K(j+1,i+1)

證明要四邊形不等式!!!

如果對於任意的a1≤a2< b1≤b2,有不等式

m(a1,b1)+m(a2,b2)m(a1,b2)+m(a2,b1)
成立,則m(i,j)滿足四邊形不等式。

由上面所設,i≤j,所以套不等式:

K(i,i)+K(i+1,j+1)K(i,j+1)+K(i+1,j)

i≤j,而i+1就不一定了,於是K(i+1,j)無意義,扔掉。
就是:
K(i,i)+K(i+1,j+1)K(i,j+1)

所以,枚舉k就可改成K(i,j-1)~K(i+1,j)。這兩個是早就算過的,從而降至O(n2)

總結一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章