给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和一个整数 k 。
你可以执行下述 递增 运算 任意 次(可以是 0 次):
从范围 [0, n - 1] 中选择一个下标 i ,并将 nums[i] 的值加 1 。
如果数组中任何长度 大于或等于 3 的子数组,其 最大 元素都大于或等于 k ,则认为数组是一个美丽数组 。
以整数形式返回使数组变为 美丽数组 需要执行的 最小 递增运算数。
子数组是数组中的一个连续非空元素序列。
动态规划 dp[i]表示选择第i个数字后需要最小递增运算数
则dp[i] = min(dp[i-1], dp[i-2], dp[i-3]) = max(nums[i] - k, 0);
class Solution {
public:
long long dp[100005];
long long minIncrementOperations(vector<int>& nums, int k) {
memset(dp,0,sizeof dp);
dp[0] = max(0,k - nums[0]);
dp[1] = max(0,k - nums[1]);
dp[2] = max(0,k - nums[2]);
int n=nums.size();
for(int i=3;i<n;i++){
dp[i] = max(0,k - nums[i]) + min(min(dp[i-2], dp[i-1]), dp[i-3]);
}
return min(min(dp[n-2], dp[n-1]), dp[n-3]);
}
};
可以使用O(1)的空间复杂度
class Solution {
public:
// long long dp[100005];
long long minIncrementOperations(vector<int>& nums, int k) {
long long f0 = 0, f1 = 0, f2 = 0;
int n=nums.size();
for(int i=0;i<n;i++){
long long inc = f2 ;
f2 = max(0, k - nums[i]) + min(min(f0, f1), f2);
f0 = f1, f1 = inc;
}
return min(min(f0, f1), f2);
}
};
节点 0 处现有一棵由 n 个节点组成的无向树,节点编号从 0 到 n - 1 。给你一个长度为 n - 1 的二维 整数 数组 edges ,其中 edges[i] = [ai, bi] 表示在树上的节点 ai 和 bi 之间存在一条边。另给你一个下标从 0 开始、长度为 n 的数组 coins 和一个整数 k ,其中 coins[i] 表示节点 i 处的金币数量。
从根节点开始,你必须收集所有金币。要想收集节点上的金币,必须先收集该节点的祖先节点上的金币。节点 i 上的金币可以用下述方法之一进行收集:
- 收集所有金币,得到共计 coins[i] - k 点积分。如果 coins[i] - k 是负数,你将会失去 abs(coins[i] - k) 点积分。
- 收集所有金币,得到共计 floor(coins[i] / 2) 点积分。如果采用这种方法,节点 i 子树中所有节点 j 的金币数 coins[j] 将会减少至 floor(coins[j] / 2) 。
返回收集 所有 树节点的金币之后可以获得的最大积分。
方法1 记忆化搜索,自顶向下
class Solution {
public:
const static int N=1e5+5;
int h[N],e[N<<1],nx[N<<1];
int cnt=1;
void add(int a,int b){
e[cnt] = b, nx[cnt] = h[a], h[a] = cnt++;
}
vector<int> c;
int k1;
int dp[N][15];
int dfs(int u,int f,int i){
if(dp[u][i]!=-1)return dp[u][i];
if(i>=14)return 0;
int ans0 = (c[u]>>i) - k1, ans1 = c[u]>>(i+1);
for(int j=h[u];j;j=nx[j]){
int t=e[j];
if(t!=f){
ans0 += dfs(t, u, i);
ans1 += dfs(t, u, i+1);
}
}
return dp[u][i] = max(ans0, ans1);
}
int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {
memset(h,0,sizeof h);
memset(e,0,sizeof e);
memset(nx,0,sizeof nx);
memset(dp,-1,sizeof dp);
c = coins;
k1 = k;
for(int i=0;i<edges.size();i++){
int a = edges[i][0], b = edges[i][1];
add(a, b);
add(b, a);
}
return dfs(0,-1,0);
}
};
方法二 树状dp, 顶底向上
class Solution {
public:
const static int N=1e5+5;
int h[N],e[N<<1],nx[N<<1];
int cnt=1;
void add(int a,int b){
e[cnt] = b, nx[cnt] = h[a], h[a] = cnt++;
}
vector<int> c;
int k1;
int dp[N][15][2];
void dfs(int u,int f){
for(int i=0;i<15;i++){
dp[u][i][0] = (c[u]>>i) - k1;
dp[u][i][1] = c[u]>>(i+1);
}
for(int j=h[u];j;j=nx[j]){
int t=e[j];
if(t!=f){
dfs(t,u);
for(int i=0;i<14;i++){
dp[u][i][0] += max(dp[t][i][0], dp[t][i][1]);
dp[u][i][1] += max(dp[t][i+1][0], dp[t][i+1][1]);
}
}
}
}
int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {
memset(h,0,sizeof h);
memset(e,0,sizeof e);
memset(nx,0,sizeof nx);
memset(dp,0,sizeof dp);
c = coins;
k1 = k;
for(int i=0;i<edges.size();i++){
int a = edges[i][0], b = edges[i][1];
add(a, b);
add(b, a);
}
dfs(0,-1);
return max(dp[0][0][0],dp[0][0][1]);
}
};
比赛过程中使用树状dp,一直设置二维dp[N][15], 发现无法做,需要设置三维dp[N][15][2],第二维记录前面已经进行多少次2操作,第三维记录当前选择1操作还是2操作,这和以往的树形dp不太一样,通常树形dp是父节点的操作不会影响子节点,这里会影响,所以要多设置一维记录上面节点的2操作影响,1操作不会影响,所以不用记录。