Tree DP Exercise

Posted by Plakhotnik on Sat, 05 Feb 2022 19:56:43 +0100

The longest path of a tree

Title link: The longest path of a tree
Analysis: We need DP on the tree, it is difficult to represent the state. We want to find the longest path, so we draw a tree at will

Code implementation:

#include<iostream>
using namespace std;
const int N=1e4+10,M=2*N;
int n,h[N],w[M],ne[M],idx=1,e[M],ans;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dfs(int u,int father){
    int d1=0,d2=0;//d1 records the longest edge, d2 records the second longest edge
    for(int i=h[u];i;i=ne[i]){//Traverse all outbound edges
        int j=e[i];
        if(j==father)   continue;//If it's his father, skip it, and stop searching here
        int d=dfs(j,u)+w[i];//Find the longest plus w[i] in the subtree with j as the root node
        if(d>=d1){//Update Longest Edge and Second Longest Edge
            d2=d1;
            d1=d;
        }
        else if(d>d2){//Update Second Long Edge
            d2=d;
        }
    }
    ans=max(ans,d1+d2);//Update Answer
    return d1;//Return to Longest Edge
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);//Add two edges
        add(b,a,c);
    }
    dfs(1,0);//The second parameter passes in an unused node at will, and the first node is at will, because it can be converted into a tree regardless of which node it starts from.
    printf("%d\n",ans);
    return 0;
}

Center of Tree

Title link: Center of Tree
Analysis: This is a good tree DP question, we can pick a point at will, walk all points up, then all points down, update the answers and explain the code in detail.
Code implementation:
More details in the code

#include<iostream>
using namespace std;
const int N=1e4+10,M=2*N,INF=1e9;
int n,h[N],e[M],ne[M],w[M],idx=1;
bool is_leaf[N];
int d1[N],d2[N],p[N],up[N];//d1[i] denotes the longest downward path from point i, d2 the second longest, p the point through which the longest downward path passes (record the nearest point), and up the longest upward path
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dfs_d(int u,int father){//Search down on topics similar to those above
    d1[u]=d2[u]=0;
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        if(j==father)   continue;
        int d=dfs_d(j,u)+w[i];
        if(d>=d1[u]){
            d2[u]=d1[u];
            d1[u]=d;
            p[u]=j;
        }
        else if(d>=d2[u]) d2[u]=d;
    }
    return d1[u];
}
int dfs_u(int u,int father){//Go up
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        if(j==father)   continue;
        //Here, when u is 1, up[u]=0, but this does not affect the transfer, because it must take the longest downward path and turn to the second longest.
        if(p[u]==j) up[j]=max(d2[u],up[u])+w[i];//If the longest way down from u passes j, then the longest way up from point J is to go up or turn to the second longest way down
        else up[j]=max(up[u],d1[u])+w[i];//Otherwise, just choose one from the turn to the longest downward and upward
        dfs_u(j,u);
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    dfs_d(1,0);//down
    dfs_u(1,0);//Up
    int res=d1[1];//Note that traversing all points of Node 1 in this topic is equivalent to walking down
    for(int i=2;i<=n;i++){//Other points need to take the maximum value from the maximum up and the minimum value from the maximum down and the minimum value from the res
        res=min(res,max(up[i],d1[i]));
    }
    cout<<res<<endl;
    return 0;
}

Digital Conversion

Title link: Digital Conversion
Analysis: This question is still interesting. We preprocess and connect all the edges that can be connected, then we need to find the longest path to get back to the first question. It is important to note that there may be multiple trees in this topic, that is, not all points are connected, so we will search through each point.
Code implementation:

#include<iostream>
using namespace std;
const int N=5e4+10;
int idx=1,h[N],ne[N],e[N],sum[N];
int d1[N],d2[N],res;
bool st[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    if(d1[u])   return ;//Return directly to the longest if searched
    st[u]=1;//Mark it after searching
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        dfs(j);
        if(d1[j]+1>d1[u])   d2[u]=d1[u],d1[u]=d1[j]+1;
        else if(d1[j]+1>d2[u])  d2[u]=d1[j]+1;
    }
    res=max(res,d1[u]+d2[u]);
}
int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++){//First deal with the sum of all the divisors
        for(int j=2;j<=n/i;j++){//Since the approximate number does not contain itself, all JS start from 2
            sum[i*j]+=i;   
        }
    }
    for(int i=2;i<=n;i++){//Add an edge from 2, otherwise add an edge from 0 to 1
        if(sum[i]<i){
            add(sum[i],i);//We don't need to search backwards, so we can create an edge from the smallest tree to the largest one.
        }
    }
    for(int i=1;i<=n;i++){//Search from the smallest point
        if(!st[i])//If not searched
         dfs(i);
    }
    cout<<res;
    return 0;
}

Binary apple tree

Title link: Binary apple tree
Analysis: This question or easy, feel a 01 backpack smell? Re-taste, feel a smell of DP? Perhaps that's the beauty of algorithms (you have me, I have you).
Code implementation:

#include<iostream>
using namespace std;
int n,q;
const int N=110,M=2*N;
int dp[N][N];//dp[i][j] denotes a subtree with I as its root node, retaining the maximum number of apples remaining in the j-root branch
int h[N],ne[M],w[M],idx=1,e[M];
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int u,int father){
    for(int i=h[u];i;i=ne[i]){
        if(e[i]==father)   continue;
        dfs(e[i],u);
        for(int j=q;j;j--){//Enumerate the number of branches reserved with u as the root node
            for(int k=0;k<j;k++){//Enumerates the number of branches reserved for a subtree with this child node as the root node
                dp[u][j]=max(dp[u][j],dp[e[i]][k]+dp[u][j-k-1]+w[i]);
            }
        }
    }
}
int main(){
    cin>>n>>q;
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);//Build two-way sides
        add(b,a,c);
    }
    dfs(1,0);
    cout<<dp[1][q];
    return 0;
}

Strategy Game

Title link: Strategy Game
Analysis: A stream of state machine model flavor, write according to that model, see the code for details.
Code implementation:

#include<iostream>
#include<cstring>
using namespace std;
const int N=1510;
int n;
int h[N],e[N],ne[N],idx=1;
int dp[N][2];//dp[i][0] denotes the minimum value when left at I and when placed at 1
bool vis[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    dp[u][0]=0,dp[u][1]=1;
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][0]+=dp[j][1];//If u-node is not placed, child nodes must be placed
        dp[u][1]+=min(dp[j][0],dp[j][1]);//If u-node is placed, child nodes can be left alone
    }
}
int main(){
    while(scanf("%d",&n)!=-1){
        memset(h,0,sizeof h);
        idx=1;
        memset(vis,0,sizeof vis);
        for(int i=1;i<=n;i++){
            int a,b;
            scanf("%d:(%d)",&a,&b);
            while(b--){
                int c;
                scanf("%d",&c);
                add(a,c);
                vis[c]=true;//Mark it as not the root node
            }
        }
        int root=0;
        while(vis[root])    root++;//Find the root node
        dfs(root);
        printf("%d\n",min(dp[root][0],dp[root][1]));
    }
    
    return 0;
}

Palace guards

Title link: Palace guards
Analysis: Note that this topic is different from the previous one. The previous one is the observation edge, and this one is the observation point. In fact, the ideas are similar, see the code for details.
Code implementation:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1510;
int n;
int h[N], w[N], e[N], ne[N], idx;
int f[N][3];//f[i][0] means point I is observed by its parent node, f[i][1] means watched by its child node, and f[i][2] means watched by itself
bool st[N];
void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u){
    f[u][2] = w[u];
    int sum = 0;
    for (int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        dfs(j);
        f[u][0] += min(f[j][1], f[j][2]);//If this node is watched by a parent node, its children must look for a son or look at it themselves
        f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);//If this node looks at itself, its children are free
        sum += min(f[j][1], f[j][2]);//sum is used when f[u][1], in which case all the child nodes have to depend on themselves or their sons to add up
    }
    f[u][1] = 1e9;//For f[u][1], we want to select the best child node, so initialize to a large number and then take the minimum case value
    for (int i = h[u]; ~i; i = ne[i]){//Enumerate all the child nodes, which are guarded, then the total price is minus min(f[j][1], f[j][2]) plus f[j][2]
        int j = e[i];
        f[u][1] = min(f[u][1], sum - min(f[j][1], f[j][2]) + f[j][2]);
    }
}
int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ){
        int id, cost, cnt;
        cin >> id >> cost >> cnt;
        w[id] = cost;
        while (cnt -- ){
            int ver;
            cin >> ver;
            add(id, ver);
            st[ver] = true;//Under non-root node tag
        }
    }
    int root = 1;
    while (st[root]) root ++ ;//Find the root node
    dfs(root);
    cout << min(f[root][1], f[root][2]) << endl;
    return 0;
}

Tree DP's Title feels interesting, but also brush more on the Logu!

Topics: Algorithm Dynamic Programming