樹形DP學習筆記

概念:

樹形dp就是....我也不知道是什麼,反正一個主件下面有不少的附件可選就是樹形dp,咕咕咕node

dp方程 (並不適用於所有的樹形dp)

樹形dp的主要實現形式是dfs,在dfs中dp,主要的實現形式是dp[i][j](j=0或者是1),i是以i爲根的子樹,j是表示在以i爲根的子樹中選擇j個子節點,0表示這個節點不選,1表示選擇這個節點。有的時候j或0/1這一維能夠壓掉ios

選擇節點類:
\[dp[i][0]=dp[j][1] dp[i][1]=max/min(dp[j][0],dp[j][1])\]數組

樹形揹包類:
\[dp[v][k]=dp[u][k]+val dp[u][k]=max(dp[u][k],dp[v][k−1])\]app

其實我以爲就是這樣的:
dp[i][j]並不表示以i爲根,選j個物品,而是從根節點到i的路徑及其左邊全部的節點,以及以i爲根的子樹的全部節點中,容量爲j的最大價值。上述方程應該用到了泛化物品的方法函數

泛化物品:

定義
考慮這樣一種物品,它並無固定的費用和價值,而是它的價值隨着你分配給它的費用而變化。這就是泛化物品的概念。學習

更嚴格的定義之。在揹包容量爲V的揹包問題中,泛化物品是一個定義域爲0..V中的整數的函數h,當分配給它的費用爲v時,能獲得的價值就是h(v)。ui

這個定義有一點點抽象,另外一種理解是一個泛化物品就是一個數組h[0..V],給它費用v,可獲得價值h[V]。spa

一個費用爲c價值爲w的物品,若是它是01揹包中的物品,那麼把它當作泛化物品,它就是除了h(c)=w其它函數值都爲0的一個函數。若是它是徹底揹包中的物品,那麼它能夠當作這樣一個函數,僅當v被c整除時有h(v)=v/cw,其它函數值均爲0。若是它是多重揹包中重複次數最多爲n的物品,那麼它對應的泛化物品的函數有h(v)=v/cw僅當v被c整除且v/c<=n,其它狀況函數值均爲0。code

一個物品組能夠看做一個泛化物品h。對於一個0..V中的v,若物品組中不存在費用爲v的的物品,則h(v)=0,不然h(v)爲全部費用爲v的物品的最大價值。P07中每一個主件及其附件集合等價於一個物品組,天然也可看做一個泛化物品。blog

泛化物品的和
若是面對兩個泛化物品h和l,要用給定的費用從這兩個泛化物品中獲得最大的價值,怎麼求呢?事實上,對於一個給定的費用v,只需枚舉將這個費用如何分配給兩個泛化物品就能夠了。一樣的,對於0..V的每個整數v,能夠求得費用v分配到h和l中的最大價值f(v)。也即

f(v)=max{h(k)+l(v-k)|0<=k<=v}

能夠看到,f也是一個由泛化物品h和l決定的定義域爲0..V的函數,也就是說,f是一個由泛化物品h和l決定的泛化物品。

由此能夠定義泛化物品的和:h、l都是泛化物品,若泛化物品f知足以上關係式,則稱f是h與l的和。這個運算的時間複雜度取決於揹包的容量,是O(V^2)。

泛化物品的定義代表:在一個揹包問題中,若將兩個泛化物品代以它們的和,不影響問題的答案。事實上,對於其中的物品都是泛化物品的揹包問題,求它的答案的過程也就是求全部這些泛化物品之和的過程。設此和爲s,則答案就是s[0..V]中的最大值。

揹包問題的泛化物品
一個揹包問題中,可能會給出不少條件,包括每種物品的費用、價值等屬性,物品之間的分組、依賴等關係等。但確定能將問題對應於某個泛化物品。也就是說,給定了全部條件之後,就能夠對每一個非負整數v求得:若揹包容量爲v,將物品裝入揹包可獲得的最大價值是多少,這能夠認爲是定義在非負整數集上的一件泛化物品。這個泛化物品——或者說問題所對應的一個定義域爲非負整數的函數——包含了關於問題自己的高度濃縮的信息。通常而言,求得這個泛化物品的一個子域(例如0..V)的值以後,就能夠根據這個函數的取值獲得揹包問題的最終答案。

綜上所述,通常而言,求解揹包問題,即求解這個問題所對應的一個函數,即該問題的泛化物品。而求解某個泛化物品的一種方法就是將它表示爲若干泛化物品的和而後求之。(以上內容取自揹包九講


上圖取自國家集訓隊論文《淺談幾類揹包題》,點擊圖片能夠放大.

例題:

一.P2014 選課

傳送門

思路

題目意思是想選擇一門課,必需要先學會它的必修課,即選擇一門課必需要選擇必修課。那麼他又說要選擇的價值最大,這就是揹包問題啦。又由於他有依賴性性行爲,因此就是個樹形dp
解釋一下樣例:

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

很明顯此題的關聯關係一目瞭然,因此咱們能夠在子樹上適用範化揹包.
注意爸爸是0說明沒有爸爸
即:
設f[i][j]表示選擇以i爲根的子樹中j個節點。
u表明當前根節點,sum表明其選擇的節點的總額。

代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#define ll long long int
#define MAXN 2500
using namespace std;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
struct node {
    int next,to;
} e[MAXN];
int n,m,head[MAXN],c[MAXN],f[MAXN][MAXN],sum;
void add(int x,int y)
{
    e[++sum].next=head[x];  
    e[sum].to=y;
    head[x]=sum;
}
void build(int u,int t)
{
    if(t<=0) return;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        for(int j=0;j<t;++j)
        {
            f[v][j]=f[u][j]+c[v];
        }
        build(v,t-1);
        for(int k=1;k<=t;++k)
        {
            f[u][k]=max(f[u][k],f[v][k-1]);
        }
    }
}
int main() {
    n=read(),m=read();
    for(int i=1; i<=n; ++i) 
    {
        int a;
        cin>>a>>c[i];
        if(a!=0) 
        {
            add(a,i);
        }
        if(a==0) 
        {
            add(0,i);
        }
    }
    build(0,m);
    cout<<f[0][m];
    return 0;
}

二.P1352 沒有上司的舞會

連接:

傳送門

思路:

這道題比上面的簡單多了,咕咕咕,就是直接用第一個dp方程

代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#define ll long long int
#define MAXN 6666
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
/*
註釋掉的有bug 
struct node {
    int next,to;
    int in;
} e[MAXN];
int n,m,happy[MAXN],head[MAXN],dp[MAXN][MAXN],root,sum;
void add(int x,int y) {
    e[++sum].next=head[x];
    e[sum].to=y;
    head[x]=sum;
}
void do_it(int x) {
    for(int i=head[x]; i; i=e[i].next) {
        do_it(i);
        dp[x][1]=max(max(dp[x][1],dp[x][1]+dp[i][0]),dp[i][0]);
        dp[x][0]=max(max(dp[x][0],dp[i][1]+dp[x][0]),max(dp[i][1],dp[i][0]));
    }
}
int main() {
    n=read();
    for(int i=1; i<=n; ++i ) happy[i]=read();
    for(int i=1; i<=n-1; ++i) {
        int x,y;
        cin>>x>>y;
        e[x].in++;
        add(y,x);
    }
    int zero1,zero2;//讀掉0
    cin>>zero1>>zero2;
    for(int i=1; i<=n; ++i) {
        if(!e[i].in) {
            root=i;
            break;
        }
    }
    do_it(root);
    cout<<max(dp[root][0],dp[root][1]);
    return 0;
}
註釋掉的有bug 
*/
int ind[MAXN],n,hap[MAXN],dp[MAXN][2],fa[MAXN],root,vis[MAXN],next[MAXN],po[MAXN];
void do_it(int x)
{
    for(int i = po[x]; i; i = next[i])
    {
        do_it(i);
        dp[x][1]=max(max(dp[x][1],dp[x][1]+dp[i][0]),dp[i][0]);
        dp[x][0]=max(max(dp[x][0],dp[i][1]+dp[x][0]),max(dp[i][1],dp [i][0]));
    }
}
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>dp[i][1];
    for(int i=1; i<=n; i++)
    {
        int a,b;
        cin>>b>>a;
        ind[b]++;
        next[b]=po[a];
        po[a]=b;
    }
    for(int i=1; i<=n; i++)
        if(!ind[i])
        {
            root=i;
            break;
        }
    do_it(root);
    cout<<max(dp[root][0],dp[root][1]);
}

這篇博客是我學習了網上的大佬和國家隊徐持衡的論文後寫的,奈何個人水平過低沒法深刻的學會,只是膚淺的懂了因此可能會有一些錯誤,若是發現請評論區告訴我!

相關文章
相關標籤/搜索