We are given a string of length . Let be any string of length . The beauty of relative to is the number of pairs so that and , where means lexicographically. That is, the beauty of a string is the number of substrings which compare greater than the corresponding substring of .
Given and an integer , how many strings are there with a beauty equal to , relative to the string ?
- Substrings
- String matching / suffix arrays?
- DP
- Small ,, maybe solution
- Counting
IdeaIntuition for DPNO
The constraints and my experiences seem to imply some kind of Dynamic Programming approach where I can count the strings based on smaller strings. The table/function will probably look like . I'm not sure, yet, what the dimensions or the values would actually mean though.
At this point, I decided to follow my intuition about the dp. I couldn't easily describe the dimensions or how the sub-problems would actually be combined. Furthermore, any ideas I did have (at least mentally) for the dp seemed like they will be in the end. Instead of getting stuck here though, I decided to fully formulate a dynamic programming solution (i.e.: write out the function and what the sub-problems actually are). I was hoping that, once I got a correct answer, that I could optimize the solution later to make sure it ran efficiently.
Sometimes it is easy to come up with a dynamic programming solution (or other kind of solution) that is correct but asymptotically too slow, for a given problem. Many people, myself included, will often get stuck thinking "Well there's no point in implementing this DP solution because it is definitely too slow". Usually, this "too slow" comes from a single extra (often redundant) dimension in your state-space. For example, maybe you have dp[a][b][c], but it turns out (for whatever reason) that the only valid "c" values always satisfy the equation: c := a+b+3 Then you do not need the extra dimension. And you can reduce the state-space and (likely) the overall complexity of your solution. Other times, maybe the state-space is correctly characterized (i.e.: no redundant dimensions), but the transitions are costly in some way. For instance, instead of thinking of dp[n] as the sum of values from 1..n (which would require combining n things to compute it), we might write: dp[n] = (n'th value) + dp[n-1]. That would (implicitly) sum over all values (i.e.: by induction, if dp[n-1] is also the sum of values from 1..n-1). In this case, we did not reuse the over-lapping sub-problems nicely, and a minor adjustment could fix it. In all cases, I think it's best to simply write down the mathematical formula for the state-space and the dp. I also think it's wise to also write out PRECISELY what the dp means, eg: dp[n][k] = number of string of length n that have beauty exactly k when compared against S[1..n]. This is precise, and can be verified.
Once all of this is written down, and the formulas are derived we can often find the key observation necessary to make the dp "fast" simply by inspecting the formula. For example, we might see somewhere in the formula, a big "sum". Then we can look at ways of turning this "big sum" into (maybe) a mini-dp that may take only one-step per state to compute. Or we might find a lot of over-lapping sub-problems.
In any case, start by formulating whatever dp comes to mind intuitively. Then, once it is fully formulated, modify and "bash" it, until it is efficient or clean enough to work. If I had done this here, I would have solved the problem a lot sooner.
The dp I finally came up with, was based on the following observations.
Let's suppose we have a . What properties of do we need to guarantee in order to ensure that it has a beauty of exactly .
Can we characterize how all valid would look?
Suppose we have some string with beauty . Then there are sub-strings which compare greater than their respective substring in . Each such substring looks as follows: It compares equal on a certain number of characters; then there is some character which is greater; then the remaining characters can be anything. Formally, for a pair of indices contributing to the beauty: there exists an index so that , , and and the characters have no specific relation to . In some sense, it is that single character being greater than that characterizes the substring and the pair . So, we observe that we should focus on particular occurrences of characters that compare greater
Given a set of elements which may be "useful" or "good" (whatever that means in a given context), it is often a good idea to look at the "extreme" elements. For example, if the set is a set of numbers, look at the minimum element; or if (in this case), the set of "useful" objects is a set of characters (i.e.: we are concerned with the characters that compare greater), look at the first one (i.e.: one with minimal index).
The above key observation and problem solving process lead to a possible characterization of the dp states and transitions between them. They would be based on the concept of picking a first "differing" character, and then recursively using the results of other dp states.
This solution is described further below. The above information should be enough to provide an intuition for how to solve this problem.
IdeaDP FormulationTLE
Note: I had a couple failed attempts at a solution before arriving at a final dp.
Recall (from the last observations and problem solving processes in the idea above), that we want to characterize our dp solution strings () based on their first character that is bigger than the corresponding character in . More generally, let's characterize them by the first character that differs from the corresponding character in . That is, it may be bigger or smaller, and we might have to handle these cases differently. This leads to a definition (and characterization).
For any string , we say a "block" in is any substring so that but . A "big block" is any such where . And a "small block" is any such where .
Every string can be uniquely decomposed into a sequence of (non-overlapping) blocks.
Let be the index of the first character that differs (i.e.: ). Then all characters since was the first character to differ. Thus, is a block. And then we decompose similarly (I guess it would be by induction or by repetition), and we have a sequence of blocks, as desired.
Now we attempt to formulate the dp, using the block decomposition. I write it out as a recursive function.
Let be a function which counts the number of strings that have a beauty of when matched against .
If we can somehow compute correctly for all and , then we're done, and the answer would be .
We now compute based on the block decomposition as described above. I work through the formulation step-by-step.
Every substring counted by will either compare exactly equal to or will have at least one differing character. If it has no differing characters, it necessarily has 0 beauty. And if it has at least one differing character, we consider the first differing character, which can occur in any position . So we get that:
T(n,k) = \sum_{x=n}^{N}{\left(\text{# of strings with beauty k and which have first differing character at x}\right)} + \left\{1 \text{ if } k==0\right\}
where the last "+ 1" part comes from the single string that has no differing characters (namely, if itself). So, besides the "+1", we will focus on computing the main sum directly (with the differing characters).
So, how many strings have beauty and a first differing character (among ) in a particular position ? (We use counting / combinatorics here.)
The characters must all be equal to the corresponding characters in . So there is only one choice for these. Then, character will either compare greater or smaller. And we count/handle these cases separately.
If , then there are choices (using ASCII subtraction; i.e.: , and , etc.) for character . The remaining characters can be anything, as long as the total beauty ends up being . How much beauty do we get from using this first block? Well, any substring that has and will contribute to the beauty (because it will start with some equal characters, and then compare greater exactly at position , so it will be greater overall). And since we are considering only the indices we get exactly choices for , and choices for . So, this block contributes exactly: to the beauty.
And notice, all other indices that contribute to the beauty must have that strictly. So we could use to count the number of ways to fix the remaining characters to fill up the remaining beauty.
So, in total the number of strings which have a beauty among the indices with a first differing character for is exactly: .
Similarly, if then there are choices for , this first block adds 0 beauty, and we must choose the remaining characters in to contribute exactly beauty. So, there are exactly: strings which have a beauty among the indices with a first differing character for .
Thus, for we get the following formula.
For ,
This formula comes directly from the key observations above. If is the index of the first differing character, then either or . In the first case, we get choices and beauty, and then recur . In the second case, we have choices and 0 beauty, and then we recur on . The "+1" term comes from the string which has 0 beauty and has no differing character .
Hence, we now have a (recursive) formula to compute the answer to our question (which would be ). Also, the base case(s) for the above formula is/are: if or , also (the empty string), and for all .
So we have a correct formulation for , which can be solved recursively. What is the total running time needed to compute if we use dynamic programming to memoize the states (i.e.: if we don't ever have to recompute a state twice)?
If carried out directly, the running time of the computation is .
There are states (possible inputs to the function). For each state, , it takes at worst time to compute the sum for all as described in the formula. So, in the end, it takes time.
This is too slow. However, I would say that this is 90% of the solution. I will describe in the next "idea" how to optimize this dynamic programming approach to run in time. The reader is encouraged to try getting this above solution to work first, before reading the next section (and you may end up coming up with the better / faster solution anyway).
IdeaOptimizing the DPAC
NOTE: The bulk of the solution was described in Idea 2 above. The reader should read that first. This section merely describes how to improve the DP solution to run within the time-limits. Otherwise, the reader can also simply read the "Review" section below, to see the final solution.
Recall (from the lemma in Idea 2 above), that for , .
For the first term, is a parabola which shrinks rapidly for each , before coming back up again. And if you look at the numbers (either by simulation or just intuitively), for most , . So, we are just adding up a lot of zeroes.
Is there any way to re-order the sum so that we can avoid doing extra work (i.e.: adding up a lot of 0's)?
We somehow want to "amortize" the costs of the work done. That is, for a fixed state , there may be work done; but we want to show that the total sum of all work done is relatively small, such as rather than .
I want to transform/rearrange the sum to ensure that I am not adding lots of zeroes and hopefully find a better bound on the amount of work I have to do. For this, instead of asking: "for a fixed , how much work must be done", I will ask "for a specific , which states will receive a non-zero contribution from in the sum when computing ?" Specifically, I am looking at the first part of the sum, in the formula for which I think is the major part that can be optimized.
So, when is ? (I.e.: whenever it is smaller than 0, the answer is obviously zero, so there is no need to count it)
By rearranging the inequality, if and only if if and only if .
The right hand side is the "difference" between and . So, for a fixed , the to which it might contribute a positive value would be those that are no more than away. For example, when , it will contribute to different values; when it will contribute to different 's; when , it will contribute to of them, etc. So, all the contribute a total of work in total for any fixed . The part comes from the fact that is the "Harmonic Number", which can be approximated by .
This would mean, in an amortized fashion, the total amount of work needed to be done (assuming we only added up those which contributed non-zero values to the sum for specific ), would be , which would run in time.
Obviously, I have skipped over a lot of details on HOW that must be done. Also, I have not accounted for "all" of the sum. There is still a term that needs to be amortized as well. This is much easier, because we can simply keep some kind of running sum of all values, and just add that as well to the sum.
The key is to "work backwards" as described above in the problem solving process. Instead of computing the formula directly for each , keep a table which stores (and it will be filled up partially). Initialize it to 0. And work backwards, for each pair . For a fixed assuming that is known, we only update the table entries () for which the index actually contributes (i.e.: only those for which is positive). You also need to keep a running sum and add to that once it is known as well (this deals with the term in the formula). And as long as you are decreasing on each iteration, we will always know by the time it is needed, and we can use it to update the for smaller , and keep going. (When I say "update", I mean, add the corresponding term to the sum, based on the formula described above for .)
For clarity, here is the pseudo-ish code.
Let S[1..N] be the given string.
Let dp[n][k] be a 2D-array, initialized all to 0.
Let sum[n][k] be a 2D-array, initialized all to 0.
dp[N+1][0] = 1
sum[N+1][0] = 0 // sum[n][k] := sum { (S[x]-'a')*dp[x+1][k] : x = n..N }
// T(n,k) = sum_{x=n}^{N}{(`z`-S[x])T(x+1, k-(N-x+1)*(x-n+1)) + (S[x]-`a`)T(x+1,k)} + {1 if k==0}
// For each (x,k), we assume dp[x][k] and sum[x][k] are already computed.
// Then we update dp[n][k] for all relevant n<x and sum[x-1][k]
for x = N+1 downto 2:
x2 = x - 1 // for convenience (since we are updating x-1, not x; we have to substitute this in the formula above)
for k = 0 to K:
sum[x2][k] += (S[x2]-'a')*dp[x2+1][k]
for n = x2 downto 1:
if (k - (N-x2+1)*(x2-n+1)) <= K:
dp[n][k+(N-x2+1)*(x2-n+1)] += ('z'-S[x2])*dp[x2+1][k]
else:
break // (x2-n) <= k/(N-x2+1) - 1
return dp[1][K]I used in the above formula, because we were updating all , but not itself. So I found it easier to replace with in the formulas. Everything else remains the same though.
This solution works in time by the earlier analysis.
Review
Summary
This problem is a fairly hard example of Dynamic Programming. It requires not only coming up with a recursive function to count the number of strings, but it also requires computing this function "Bottom-up" (in an iterative fashion) rather than "Top-down" (in a purely-recursive fashion), because we need to exploit the structure of the formula in order to make it run in time.
The dp can be described as follows: Let be the number of strings which have beauty when compared against (This is the same as the recursive function that I use above in Ideas 1 - 3). Each string with beauty must have a "first differing character" in some position (assuming ). All characters before that must compare exactly equal to the corresponding characters of . If this "differing character" compares greater (i.e.: ), then there are ways to choose this character, and it adds exactly units to the beauty (i.e.: any substring with will compare "greater" than the corresponding substring , so we have exactly choices for and choices for ). So, then we must choose the remaining characters () to have exactly beauty, which is an equivalent sub-problem (it is equal to dp[x+1][k - (x-n+1)*(N-x+1)]).
A similar formula holds if the first differing character compares smaller than the corresponding character in , so that . So, we come to the formula:
where the last "+1" comes from the fact that might be a possibility (in which case it has no differing character).
By inspection, this dp formula takes time to compute, which would yield Time Limit Exceeded. However, we notice that for a given , the term whenever is really small compared to . So we work backwards, and once has been computed, we treat it as , and use it to update all with (based on the dp formula), and break early once becomes too small. We handling the other parts of the sum similarly.
It can be proven that, if computed this way, the overall running time would be which is Accepted.
-
For a pair of indices contributing to the beauty: there exists an index so that , , and and the characters have no specific relation to . This is the most intuitive observation, but it is also the key to characterizing the solution, if understood correctly.
-
Every string can be uniquely decomposed into a sequence of (non-overlapping) blocks, where a block is a substring with and . Either or . This key observation provided the recursive structure necessary to construct the dynamic programming solution. We recur based on the start of the current "block".
-
For , . This was the key formula (the DP). It was a result of careful combinatorial arguments, and a result of the recursive structure described above.
-
The function is a parabola as a function of (for a fixed ), and it often goes below zero. For a fixed , it will go to zero whenever the difference is greater than . So, if we skip all so that is under zero, we don't have to do as much work. This was important to amortize the running time of the algorithm.
-
This problem looks like a Dynamic Programming problem; maybe I can dp digit by digit. The dp will probably look something like .
-
Most of the immediate DP ideas that came to my mind seemed too slow. However, instead of getting caught up on these details, I decided to focus on deriving at least one correct DP formulation, regardless of how inefficient it was. This was crucial. I would not have been able to solve the problem otherwise, because there was no "better DP formula". In the end, I used the same formula, but I just changed the way it was computed to make the running time better.
-
To determine the formula, I thought "suppose we have a string with beauty . Then how would it look? What properties would it have?" This was a natural set of questions to ask. And it helped characterize the solution.
-
"Pursuing Extreme Values" is a common problem-solving technique. "If you have to walk to the store, you will always take a 'first' step." Similarly, if there are blocks in the string, we focused on the first block. The rest could be handled by recursion.
-
In order to optimize the DP, I knew that I would need to use some kind of "amortized" analysis, which is a concept I've learned from experience (e.g.: See Thomas Cormen's "Introduction to Algorithms")
-
The dp formula was written as a function , but it relied on summing over values of for certain . Instead of asking: "How can I compute from the values", I asked "Given some values for , how does it contribute to various sums?". This "backward" transformation, and the order of computation, became crucial in optimizing the dp so that it would run efficiently. The reason for this was, given the set of that actually contribute a non-zero value to the sum was parabolic (there was a bunch of that did contribute, then some that didn't contribute, and then some more that did again). However, for a fixed and , the set of whose sum contributed to was linear. That is, there is a computable, contiguous, range of such that relied on . This made it easier to "stop" early, when updating the sums, and also brought down the entire amortized running time to instead of . This was a crucial application of the problem-solving process.
-
With "Hard DP" problems, the solution is often "State Optimization". This means that you have to come up with a basic DP formula that works (but may be too slow or take up too much memory), and then exploit some structure of the formula (usually overlapping subproblems, redundant state-variables, using one of your dimensions in the answer rather than as the dimension) to reduce either the state-space or the amount of transitions/computations needed to compute the answer for a single state. This problem was exactly that. The best way to approach this is to ignore the optimizations until you have a working formula, and then spend all your time and effort working on the optimizations. This is a classic example of Dijkstra's "Separation of Concerns" principle.
-
"Reversing the DP" in various ways is often useful. For example, instead of computing a top-down dp (i.e.: solving it by recursion), we can do a bottom-up dp (i.e.: filling in the table row-by-row). As another example, sometimes instead of looking at "the first element in the set" first (in my case, the first differing character), it may pay off to look at "the last element in the set" first, and formulating your DP that way. And as a last example (the type that was used in this problem), instead of asking "How can the answer for state A be computed from states X,Y, and Z?", we might ask "How does the answer to state X contribute to the answers for states A,B,C?" Often, the DP can be done either way. Sometimes, "working backwards" like this just helps you to think about the problem differently. But there are cases, like in this problem, where the problem is just asymmetric and it is actually only plausible (or more efficient) to compute your dp in one direction rather than the other. Many problems appear symmetric, or at least don't immediately stand out as being asymmetric; but I often get stuck on hard DP problems simply because I am thinking about it in the wrong direction. So as a rule, it is good to think about DP problems from multiple angles. The "angles" I used as examples above are probably the most common ones (or at least the ones I use most often). And this "work backwards" reasoning extends to all problem-solving. That is why I have it as a "Problem-Solving Technique / Process Point".
-
Dynamic Programming / Hard DP / DP with State Optimizations / Bottom-Up DP
-
Strings / DP on Strings and Digits
-
Combinatorics / Counting DP / Number of Ways / Size of Set
-
Combinatorics / Counting DP / Number of Ways / Size of Set
-
Math / Formulas / Recurrences
Related Problems
UVa 10154 - Weights and Measures. This is an unrelated problem (by content), but it is indeed a hard DP problem that requires thinking outside the box and transforming the problem. UVa 1579 - Matryoshka. This is not really related. In fact, I don't even know the solution to this problem. I was thinking about another problem titled "Matryoshka Dolls" from some contest, which was equivalent to the Weights and Measures problem I posted just above; and I happened to come across this problem here, which is a completely different problem with a similar name. I still include it in this list, because, at first glance, maybe it is a DP problem anyway? And I might as well try to solve it. What if the beautiful substrings were those that compared "less"? Then the problem would be symmetric. What if the beautiful substrings were those that compared "different" than ? Then the problem would be a bit easier, but would still require the DP state optimizations (I think). It would be more regular, and the formula would be similar, except it would be: (or something similar), because the character need only differ (so there are 25 choices for the character), and all such substrings contribute to the beauty. What if the beautiful substrings were those that compared "equal" to the substrings of ? This problem would be different, a bit more regular (and easier to compute), but would use the same principle: Pick the first "differing" character. But this time, the beautiful substrings would be those that did not use it, so there would be a formula like: (or something similar). Always though, we would need to optimize the state (I think), although this problem is much more regular than the others, it seems).