題解/算法 {2920. 收集所有金币可获得的最大积分}
@LINK: https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes/
;
首先讀題要仔細, 首先把所有的測試樣例 按照你的思路 看能不能對應上;
把操作2看做是(把以c
為樹根的子樹的所有節點 都執行A[c]/=2
, 即該子樹的價值為 所有節點的和除以2, 即一旦c
執行操作2 那麼他的所有兒子節點都必須執行操作2), 這你就錯誤了;
仔細讀題 最起碼把樣例通過了, 按你這種思路 樣例都通過不了, 他說的是 當前點的價值是A[c]/=2
同時把他的所有兒子都變成A[i]/=2
, 但他兒子的價值 不一定就是A[i]/=2
而是要單獨考慮的, 即他也要選擇進行操作1/2;
因此 如果寫成DP(i,j)
表示以i
為根的子樹 且i
點進行操作j
的 最大價值, 這就錯誤了, 因為i
點 他會受到祖先節點的影響 即如果他的祖先節點執行了操作2 那麼此時A[i]
的值 會是A[i]/{2/4/8/...}
而不是A[i]
;
定義DP(i,j)
表示以i
為根的子樹 且他的祖先節點裡已經執行了j
次操作2, 即此時i
點的節點值為A[i]/ (1<<j)
, 然後再考慮該點 執行操作1/操作2;
如果這樣定義, 這個j
會很大 最大是N1e5
, 而所有節點都是<=1e4
的, 即當j=15
時 其實此時以i
為根的子樹 所有節點都是0
了已經(當然不意味著 他們就一定是選擇操作1/2, 不考慮這個問題) 我們這裡關注的重點是: [此時j=15
的這個數 他和任意的j>15
的樹 是完全一樣的], 因為完全一樣 自然答案也一樣, 即DP(i,j>15) = DP(i,15)
;
#錯誤#: DP(i,j)
當(1<<j) > A[i]
時, 此時A[i]/ (1<<j) == 0
, 所以此時所有的DP(i,>j) == DP(i,j)
;
這是錯誤的;
因為我們判斷的是 以i
為根的這個子樹 是否相同, 而不是i
這個節點是否相同, 比如i
這個節點 A[i]=1
, 那麼當j>=1
時 此時A[i]
就等於0了, 但是 他的兒子 比如是A[j]=1e4
, 那麼顯然1e4/2 != 1e4/4
;
因此 我們判斷是 所有點的最大值的最大的j
, 而不是當前點的;
static int DP[ 100005][21];
memset( DP, -1, sizeof( DP));
function<int(int,int,int)> Dfs = [&]( int _cur, int _fa, int _div){
if( _div >= 15){
_div = 15;
}
if( DP[_cur][ _div] != -1){ return DP[_cur][ _div];}
auto & curDP = DP[_cur][ _div];
int a0 = (A[_cur] / (1 << _div)) - K; // 操作1
int a1 = (A[_cur] / (1 << _div) / 2); // 操作2
for( int nex, edge = G.Head[_cur]; ~edge; edge = G.Next[edge]){
nex = G.Vertex[edge];
if( nex == _fa){ continue;}
a0 += Dfs( nex, _cur, _div);
a1 += Dfs( nex, _cur, _div + 1);
}
curDP = max( a0, a1);
return curDP;
};
return Dfs( 0, -1, 0);
以上是正確的代碼;
看一個錯誤的代碼; 如果你把上面的記憶化DP, 寫成是: 把DFS的返回值去掉 然後直接去使用DP[nex][_cur]
, 即把a0 += Dfs( nex, _cur, _div);
改成是Dfs( nex, _cur, _div); a0 += DP[nex][_div]
;
這就錯誤的;
當然 對於一般的記憶化DP, 這是等價的 沒問題的, 但這裡比較特殊, 因為你DFS(i,j)
並不一定對應著就是DP[i][j]
這是這個題獨特之處, 比如當j==15
時, 此時你會調用DFS(nex, 16)
上面正確的代碼 他返回的答案 其實是DP[nex][15]
而不是DP[nex][16]
, 但是你這裡錯誤的代碼 他是a1 += DP[nex][ _div + 1]
, 即此時越界了 因為DP(nex, 16)
這個值 並在我們的DP裡面!
.
那麼你可能認為 應該是WA, 而事實是 他會是TLE… 這是因為 一個極端的例子 1-2-3-4-5-...-N
, 此時你調用DP[N][16]
他等於初始值 即memset
的值 為-1
, 因此對於DP(N-1,15)
假如他的a0
值為負數 然後此時a1 = DP[N][16] = -1
, 因此 導致DP(N-1,15) = max(-1, <0) = -1
, 因此 並沒有做到記憶化 以後還會重複訪問這些點 所以是TLE;