题目链接:https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes/description/
解题思路:
最大积分(最优化解)+树形结构 + n~1e5 基本可以确定这道题要么o(n)(贪心,记忆化)要么dp。
尝试用dp来解,有一个主要洞察点:除以2这个操作其实执行不了多少次数就会被除没(变成0)。
题中的两种收集金币的方式一种会导致收集金币值-k,另一种会导致当前收集金币减半且后续子树上的金币全部减半。其中子树金币减半这个操作如果用代码来模拟则会消耗巨大的时间,所以我们考虑能否对这个操作进行记忆化或者说是dp,从而优化时间复杂度。
如果你能观察到上面的洞察点,那么dp就很好解决了,也就是说除2的次数可以被当成一个维度来构造dp算法(因为10000最多除14次就变成0了)。
定义 dp[i][j] 为 i号节点对应的子树 在 除以2^j以后 能够得到的最大值。
那么状态转移也顺理成章的出来了:
m a x ( ∑ x d p [ x ] [ j ] + c o i n s [ i ] / 2 j − k , ∑ x d p [ x ] [ j + 1 ] + c o i n s [ i ] / 2 j + 1 ) max(\sum_{x} dp[x][j]+coins[i]/2^j-k\quad, \sum_{x} dp[x][j+1]+coins[i]/2^{j+1}) max(∑xdp[x][j]+coins[i]/2j−k,∑xdp[x][j+1]+coins[i]/2j+1)
CPP代码:
class Solution {
public:
void dfs(int cur,int par,vector<vector<int>>& edge, vector<vector<int>>& dp, vector<int>& coins, int k)
{
for(int i=0;i<edge[cur].size();i++)
if(edge[cur][i]!=par)dfs(edge[cur][i],cur,edge,dp,coins,k);
for(int j=0;j<15;j++) //2^14=16,384 > 10,000,顶多除14次就为0了,这里设大点也没关系
{
int sum1=0,sum2=0;
for(int i=0;i<edge[cur].size();i++)
{
int x=edge[cur][i];
if(x==par)continue;
sum1+=dp[x][j];
sum2+=dp[x][j+1];
}
dp[cur][j]=max(sum1+(coins[cur]>>j)-k,sum2+(coins[cur]>>(j+1)));
}
return ;
}
int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {
vector<vector<int>> edge(coins.size());
vector<vector<int>> dp(coins.size(),vector<int>(16,0));
for(int i=0;i<edges.size();i++)
{
edge[edges[i][0]].push_back(edges[i][1]);
edge[edges[i][1]].push_back(edges[i][0]);
}
dfs(0,-1,edge,dp,coins,k);
return dp[0][0];
}
};