DP有些求騙的東西,比如樹形DP。
怎麼說呢,樹也是個DAG,按理說,是DAG就能動規。
用題說事
先看一道題
得自己建樹,人名字符串還要映射……板噁心……
說正事。
這道題裏,上司—下屬關係就是一個天然的有向邊,利用它,從老闆開始,往下深搜就好啦~
建樹當然是好用的鏈式前向星啦~不過轉二叉樹也可以
真正寫代碼時,是看取不取他的下屬
一個下屬取,一個下屬不取……
在加上本身取不取就是這一棵子樹最大值。
再來一道題
分析下次上
給你三個題
似乎不止三個……
洛谷 P2014 選課
洛谷 P2015 二叉蘋果樹
(紫書p282)
UVa 12186 Another Crisis
UVa 1220 Party at Hali-Bula
UVa 1218 Perfect Service
題解會有的……
幾個知識點(待補充)
來自紫書、藍書、挑戰程序設計競賽
樹的最大獨立集
對於一棵 n 個結點的無根樹,選出儘量多的結點,使其中任意兩個結點均不相鄰(即最大獨立集)。
洛谷P1271就是一個最大獨立集問題,不過帶了權(幽默係數)。然而,P1271是有根的(上司)。
那麼,對於最大獨立集問題,只需隨便“提溜”一個結點“當”根,然後就很開心了。
(紫書p281)
對結點i,只有兩種決策:選和不選。即方程:
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 爲根的子樹中結點個數,則有:
然後隨便dfs就行了。
這只是把d(i)求出來了,刪除 i 時,在
中,取一個最小的f(i),此時 i 就是重心。
(紫書p281)
f(i)表示刪除 i 後,最大子樹的結點數。n-d(i)表示 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)
dp只能求最長路長度,如果只要求長度,那就開心了。
設d(i)表示以 i 爲根的子樹中根到葉子的最大距離,則有:
其中”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)表示序列i~j的總檢索次數,sum(i,j)表示i~j頻率和。
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);
}
這只是比較樸素的方法……
前方真正高能!!!
能降至
證明要四邊形不等式!!!
如果對於任意的a1≤a2< b1≤b2,有不等式
由上面所設,i≤j,所以套不等式:
i≤j,而i+1就不一定了,於是K(i+1,j)無意義,扔掉。
就是:
所以,枚舉k就可改成K(i,j-1)~K(i+1,j)。這兩個是早就算過的,從而降至