Java 如何将此问题分解为子问题并使用动态规划?

Java 如何将此问题分解为子问题并使用动态规划?,java,algorithm,dynamic-programming,Java,Algorithm,Dynamic Programming,从2019年开始,我一直在处理一个旧的竞赛问题: 您正计划参观N个旅游景点。景点编号从1到N,必须按此顺序参观。你每天最多可以参观K个景点,并希望行程安排尽可能少的天数 在这些限制条件下,您希望在每天游览的景点之间找到一个平衡的时间表。准确地说,我们给吸引力i分配了一个分数ai。给出时间表后,每天的分数等于当天参观的所有景点的最高分数。最后,将每天的分数相加,得出日程安排的总分。使用尽可能少的天数,时间表的最大可能总分是多少 很明显,这是一个动态规划类型的问题,我可以看到它是如何产生的,但我似

从2019年开始,我一直在处理一个旧的竞赛问题:

您正计划参观N个旅游景点。景点编号从1到N,必须按此顺序参观。你每天最多可以参观K个景点,并希望行程安排尽可能少的天数

在这些限制条件下,您希望在每天游览的景点之间找到一个平衡的时间表。准确地说,我们给吸引力i分配了一个分数ai。给出时间表后,每天的分数等于当天参观的所有景点的最高分数。最后,将每天的分数相加,得出日程安排的总分。使用尽可能少的天数,时间表的最大可能总分是多少

很明显,这是一个动态规划类型的问题,我可以看到它是如何产生的,但我似乎无法理解如何将它分解为子问题,以及每个子问题如何相互关联,特别是当有两个变量N和K时

我提出了一个递归蛮力算法,该算法适用于较小的输入,但在输入过大时失败:

int daysNeeded = (int) Math.ceil((double) N / K);

// n - index of current attraction being visited
// d - days used up
public static long solve(int n, int d) {
    if (d == daysNeeded) { // Base case, stop once we reach the min days required
        if (n == N) // If we visited all attractions, count this answer
            return 0;
        else // If we didn't visit all attractions, don't count this answer
            return Integer.MIN_VALUE;
    }
    
    long result = 0;
    
    // Try to visit attractions up to K
    //
    // i + 1 is the number of attractions to visit in this day
    for (int i = 0; i < K; i++) {
        if (n + i >= N)
            break;
        
        long highestScore = attractions[n];

        // Find the attraction from [n + j ... n + i] with the highest score
        for (int j = 1; j <= i; j++) {
            highestScore = Math.max(highestScore, attractions[n + j]);
        }
        
        long next = highestScore + solve(n + i + 1, d + 1);
        
        // Find the attraction with the highest score out of all attractions from 0 to i
        result = Math.max(result, next);
    }
    
    return result;
}
int daysneed=(int)Math.ceil((double)N/K);
//n-当前被访问景点的指数
//d天用完了
公共静态长解算(int n,int d){
如果(d==daysNeeded){//基本情况,在达到所需的最小天数后停止
如果(n==n)//如果我们参观了所有景点,数一数这个答案
返回0;
否则//如果我们没有参观所有的景点,不要计算这个答案
返回Integer.MIN_值;
}
长结果=0;
//尽量去游览景点到K
//
//i+1是指当天游览的景点数量
for(int i=0;i=n)
打破
长期最高分数=景点[n];
//从得分最高的[n+j…n+i]中找到吸引力

对于(int j=1;j我将尝试以递推关系的形式给出解。 设m为参观所有景点的天数,设P[m][N]为您在m天内参观N个景点所获得的最佳值。我们还不知道P,但我们将对此进行推理

p[m][N]=max{i到k}(p[m-1][N-i]+max{l=0到i-1}(a[l]))

例如,如果您在最后一天只游览了最后两个景点,就得到了最佳分数,那么当天的分数为max(a[N],a[N-1]),总(最佳)分数为

p[m][N]=最大(a[N],a[N-1])+在m-1天内游览N-2个景点的最佳分数

这与上面的公式完全相同

p[m][N]=max(a[N],a[N-1]+p[m-1][N-2]


请注意,i>N/k(m-1)有一个限制,因为如果您在最后一天没有参观足够的景点,那么剩下的几天可能不足以参观其余的景点。

让我们从每天分配
k
景点开始,最后一天除外,它的长度为
m=N mod k
。例如:

5 3
2 5 7 1 4

2 5 7|1 4  (M = 5 mod 3 = 2)
请注意,我们不能延长任何
K
长度天数,也不能缩短任何天数,除非我们先延长较小的
M
长度天数。请注意,我们可以延长的最大金额等于
K-M=K-(N mod K)

现在,让
dp[d][m]
代表天数
1…d
的最佳分数,当天数
d+1
已扩展到
m
景点开始的
d
第天。调用所需天数
d=ceil(N/K)
。然后:

dp[1][m] = max(attractions[0..k-m-1])

dp[D][m] = max(attractions[i-m..j]) + dp[D-1][m]

dp[d][m] = max(attractions[i-l..j-m]) + dp[d-1][l]

  where (i, j) mark the starting dth day
  and 0 ≤ l ≤ m
答案将是
dp[D][m]
中最好的一个

我们可以将
O(1)
中相关最大值的计算折叠到例程中:从左到右预处理
O(n)
中每个起始部分(表示天数)的前缀最大值。对于
max(吸引力[i-l..j-m])的每个循环
,从前缀max中
j-m
处提供的最大值开始,然后通过将当前值与每个
景点[i-l]
进行比较来更新最大值,因为
l
是递增的

总体复杂性似乎是
O(ceil(N/K)*(K-(N mod K))^2)

在时间方面,我们可以做得更好,通过观察随着
m
的增加,我们可以跳过
l
上的迭代,如果起始最大值没有变化,或者之前选择了一个大于起始最大值的最大值(这意味着它来自
i
)在这种情况下,我们只需要考虑新的<代码> L>代码>,它比我们以前检查的要大一点。我们可以依靠右到左前缀MAX加上我们的左到右前缀MAX,在<代码> O(1)< /代码>中获得这个新的MAX。 在我们的简单示例中,我们有:

2 5 7 1 4

dp[1][0] = max(2, 5, 7) = 7
dp[1][1] = max(2, 5) = 5

dp[2][0] = max(1, 4) + dp[1][0] = 11
dp[2][1] = max(7, 1, 4) + dp[1][1] = 12

如果你缓存
solve(n,d)
你就有了一个动态规划解决方案。我不明白“daysNeeded”(n除以K)有什么意义?这看起来像一个优化问题,但它有两个目标函数,并且不清楚哪一个被赋予优先级-最大分数还是最小天数?您的解决方案假设天数最多为N/K(类似于背包容量)但是我在问题陈述中看不到任何关于这个约束的东西啊,而且你假设天数应该正好是N/K,但是如果天数更多,可能会有更好的分数。无论如何,问题陈述是模糊的,需要修改revised@mangusta如果我们可以有任意天数,最大可实现分数为每天只参观一个景点。天数最多不是ceil(n/k),而是ceil(n/k)我想,任何一天我们都可以缩小K。假设K是3,N是8,那么有效分布可以是第一天3,第二天2,第三天3day@ShubhamAgrawal我同意,我的描述也是。哪一部分让你不这么认为?