1. 程式人生 > 實用技巧 >對於樹上狀態機dp問題的一些總結與思考

對於樹上狀態機dp問題的一些總結與思考

前言:全篇純屬個人理解與感悟,建議帶著批判的視角來審視。

本次部落格的一些有關例題(難度遞增)如下:AcWing285(沒有上司的舞會);AcWing323(戰略遊戲);AcWing1077(皇宮看守);洛谷P2279【HNOI2003】消防局的設立;

1.AcWing285(沒有上司的舞會)

題意:給你n個點n-1條邊,每個點有權值,一個邊最多可選一個點,問樹上最大可選權值和

題解:我們仔細審題會發現題目要求,"一個邊最多可選一個點"說明該邊上的點可選數量為0-1這兩種情況。因為是樹上問題,我們對於父親結點u來考慮。樹上狀態機dp問題無非是父親結點與孩子結點之間關係的狀態轉移。如果選了父親結點u(即定義狀態為dp[u][1]

,1表示選了該節點,0表示沒選該節點),那麼其孩子節點v必定沒選,所以我們可以推出這樣一個關係dp[u][1] = Σdp[v][0]。接下來我們就要考慮不選父親結點的情況(即狀態為dp[u][0]),因為一條邊上的點可選可不選,而我們題目又要求樹上最大權值和。那麼我們推出關係dp[u][0]=Σmax(dp[v][0],dp[v][1])。以上就是狀態轉移的所有形式,最後輸出max(dp[root][0],dp[root][1])就是本題所求的樹上最大可選權值和了。

AC程式碼:

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define
ll long long #define rep(i,a,n) for(int i=a;i<=n;i++) #define per(i,n,a) for(int i=n;i>=a;i--) #define endl '\n' #define eps 0.000000001 #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) #define IO ios::sync_with_stdio(false);cin.tie(0); using namespace std; const int INF=0x3f3f3f3f; const ll inf=0x3f3f3f3f3f3f3f3f
; const int mod=1e9+7; const int maxn=1e5+5; int tot,head[maxn]; struct E{ int to,next; }edge[maxn<<1]; void add(int u,int v){ edge[tot].to=v; edge[tot].next=head[u]; head[u]=tot++; } int n,w[maxn],fa[maxn],root; int dp[maxn][2]; void dfs(int x){ dp[x][1]=w[x]; for(int i=head[x];i!=-1;i=edge[i].next){ int v=edge[i].to; dfs(v); dp[x][0]+=max(dp[v][0],dp[v][1]); dp[x][1]+=dp[v][0]; } } int main(){ scanf("%d",&n);mem(head,-1); rep(i,1,n) scanf("%d",&w[i]); rep(i,1,n-1){ int u,v;scanf("%d%d",&u,&v); add(v,u);fa[u]=v; } root=1; while(fa[root]) root=fa[root]; dfs(root); cout<<max(dp[root][0],dp[root][1])<<endl; }
View Code

2.AcWing323(戰略遊戲)

題意:給你n個點(編號為0~n-1),每條邊至少選1個點,問最少能選幾個點?

題解:感覺和上面那個題很類似,狀態轉移也很好想。因為題目要求樹上最小點數。那麼我們對於一條邊分析,如果父親結點u沒選,那麼其孩子節點v必選,所以狀態轉移方程為dp[u][0]=Σdp[v][1]。如果父親結點u選了,那麼其孩子理論上可選可不選,但我們要求求最小點,而dp的思路是通過區域性的最小的優化轉移到整體,即以小見大,所以我們需要讓區域性最小,故轉移方程為dp[u][1]=Σmin(dp[v][0],dp[v][1]).

AC程式碼:

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1500+5;
int tot,head[maxn];
struct E{
    int to,next;
}edge[maxn<<1];
void add(int u,int v){
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,fa[maxn];
int dp[maxn][2];
void dfs(int x){
    dp[x][1]=1;
    dp[x][0]=0;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        dfs(v);
        dp[x][0]+=dp[v][1];
        dp[x][1]+=min(dp[v][1],dp[v][0]);
    }
}
int main(){
    while(~scanf("%d",&n)){
        mem(head,-1);mem(fa,0);mem(dp,0);tot=0;
        rep(i,1,n){
            int x,t;scanf("%d:(%d)",&x,&t);
            ++x;
            rep(i,1,t){
                int qwq;scanf("%d",&qwq);
                ++qwq;
                fa[qwq]=x;add(x,qwq);
            }
        }
        int root=1;
        while(fa[root]) root=fa[root];
        dfs(root);
        printf("%d\n",min(dp[root][1],dp[root][0]));
    }
}
View Code