Let’s take a step back from the DP solution here for a moment, since I think this follows from a mathematical property of the solution.
Let’s imagine that you have to make k cuts to a rope of length n. This is a bit more restrictive than the problem you’ve stated, in which you can make any (positive) number of cuts. What would the ideal solution look like? If you’re allowed to make any cut you want, not just integer cuts, the best answer would be to cut the rope into k pieces of size n/k. Why is this? Well, suppose you don’t do this. That means that some piece must be bigger than the average (let’s say its size is at least n/k + ε) and some piece must be smaller than the average (let’s say its size is at most n/k - ε). Then the product of these two pieces is at most
(n/k + ε)(n/k - ε)
= (n/k)2 - ε2.
Note that the bigger ε gets here - that is, the bigger the disparity in the sizes of the pieces - the smaller this product gets. That means that you’d be better off changing how these pieces were cut so that their sizes were closer to the average n/k.
The same logic applies even if the cuts have to be integer sizes. Imagine, for example, that two pieces have sizes that differ by at least two. Write one piece’s size as at least m + d and the other’s as at most m - d. Then the product of these pieces is m2 - d2, so you’d be better off making them as close to their average value as possible to keep d small.
So why the upper bound of n/2? Well, the above reasoning suggests we should keep the values as close together as possible if we’re making k cuts, and in particular with k cuts no piece should have a size bigger than ⌈n / k⌉. In the case where k = 2, the largest piece should never be bigger than ⌈k/2⌉. I think that’s where this bound comes from.
In fact, I’m fairly sure that this argument implies there’s a much faster solution to this problem. Rather than chopping off arbitrary amounts at each point and talking about making an arbitrary number of cuts, instead, iterate over the number of cuts made. For each number of cuts, we know what the sizes of the cuts ought to be - make the pieces’ sizes as close to the average as possible. There’s no need to use any DP to do that. In pseudocode:
max_cut = 0
for cuts = 2 to n:
average_rounded_down = n // cuts
pieces_above_average = n % cuts
pieces_below_average = cuts - pieces_below_average
value_of_this_cut = (average_rounded_down) ** pieces_below_average + (average_rounded_down + 1) ** pieces_above_average
if max_cut < value_of_this_cut:
max_cut = value_of_this_cut
This runs in time O(n), ignoring the cost of raising the values to the different powers (which, in fairness, isn’t O(1)).
I’m fairly sure this can be improved even further by using something like a binary search to improve the search for the optimal number of cuts, but alas I don’t have time right this minute to work out how to do that. :-)