GOTCHI - Editorial

I’ll have a stab at this - completely un-proof read. Please point out mistakes/ confusions :slight_smile:

The key point is that the sum of any break-down into cups of ranges is actually a sum of suffix-sums of a.

Assume 1-relative a with elements a_1, a_2, … a_N.

Assume we’ve picked an assignment of subranges into cups. We can represent this by a sequence c_1, c_2, … c_p where c_i is the index in a of the last element in cup i. (c_{i+1} > c_i for all valid i, and c_p=N).

In other words, our assignment is:

a_1, a_2, ... a_{c_1} (cup 1)
a_{c_1 + 1}, a_{{c_1} + 2}, ... a_{c_2} (cup 2)
…
a_{c_{p-2} + 1}, a_{c_{p-2} + 2}, ... , a_{c_{p-1}} (cup p - 1).
I’ll have a stab at this - completely un-proof read. Please point out mistakes/ confusions :slight_smile:

The key point is that the sum of any break-down into cups of ranges is actually a sum of suffix-sums of a.

Assume 1-relative a with elements a_1, a_2, … a_N.

Assume we’ve picked an assignment of subranges into cups. We can represent this by a sequence c_1, c_2, … c_p where c_i is the index in a of the last element in cup i. (c_{i+1} > c_i for all valid i, and c_p=N).

In other words, our assignment is:

a_1, a_2, ... a_{c_1} (cup 1)
a_{c_1 + 1}, a_{{c_1} + 2}, ... a_{c_2} (cup 2)
…
a_{c_{p-2} + 1}, a_{c_{p-2} + 2}, ... , a_{c_{p-1}} (cup p - 1).
a_{c_{p-1} + 1}, a_{c_{p-1} + 2}, ... , a_{c_p} (cup p).

$a_{c_{p-1} + 1}, a_{c_{p-1} + 2}, ... , a_{c_p}$ (cup $p$)

or in other words:

\overbrace{a_1, a_2, ... ,a_{c_1}}^{\text{cup }1},\overbrace{a_{c_1 + 1}, a_{{c_1} + 2}, ... ,a_{c_2}}^{\text{cup }2},a_{{c_2}+1},\dots, \overbrace{a_{c_{p-2} +1},\dots, a_{c_{p-1}-1},a_{c_{p-1}}}^{\text{cup }(p-1)},\overbrace{a_{c_{p-1} + 1}, a_{c_{p-1} + 2}, ... , a_{c_p}}^{\text{cup }p}

Our potency then is:

1 \times \sum_{i = 1}^{c_1} a_i + 2 \times \sum_{i = c_1+1}^{c_2} a_i + ... + (p-1) \times \sum_{i = c_{p-2} + 1}^{c_{p-1}} a_i + p \times \sum_{i = c_{p-1} + 1}^{c_p} a_i

Let’s examine the last two terms in more detail. Note that, since c_p=N, the last term is actually a suffix sum of a, multiplied by p.

(p-1) \times \sum_{i = c_{p-2} + 1}^{c_{p-1}} a_i + p \times \sum_{i = c_{p-1} + 1}^{c_p} a_i=
(p-1) \times \sum_{i = c_{p-2} + 1}^{c_{p-1}} a_i + (p - 1) \times \sum_{i = c_{p-1} + 1}^{c_p} a_i +\sum_{i = c_{p-1} + 1}^{c_p} a_i=
(p-1) \times \sum_{i = c_{p-2} + 1}^{c_{p}}a_i+\sum_{i = c_{p-1} + 1}^{c_p} a_i

What’s happened here? We started off with some random term multiplied by p-1 and a suffix sum multiplied by p. Now, we’ve got one suffix sum, and another suffix sum multiplied by p-1.

Let’s look at the last three terms that we have now:

(p-2) \times \sum_{i = c_{p-3} + 1}^{c_{p-2}} a_i + (p-1) \times \sum_{i = c_{p-2} + 1}^{c_{p}} a_i +\sum_{i = c_{p-1} + 1}^{c_p} a_i

We can now repeat the procedure for the terms beginning with (p-2) and (p-1), ending up, I think, with:

(p-2) \times \sum_{i = c_{p-3} + 1}^{c_{p}} a_i + \sum_{i = c_{p-2} + 1}^{c_{p}} a_i +\sum_{i = c_{p-1} + 1}^{c_p} a_i

So the term beginning with (p-2) has now also become a suffix sum (multiplied by (p-2)).

In this way, we gradually, from the right hand side, break down our original sum into a sum of p (distinct) suffix sums - a formal proof via induction would probably be quite easy, but I can’t be bothered :). Finding the p suffix sums with minimum sum should then give us our minimum potency.

8 Likes

@ssjgz as @vijju123 said, you should definitely be applying for an Editorialist at Codechef.

3 Likes

I actually thought of solving it using DP(like Matrix Chain Multiplication) , which I think, is although correct would have timed out. When would I be able to think like this. :pensive:

lol - I did the same thing (after the contest, of course :)). Since N<=10^4 and P<=10^4, an O(N \times P) solution would probably just about work :slight_smile:

May as well include it, in all its crappy glory:

Possible DP-based solution
// Simon St James (ssjgz) - 2019-XX-XX
//#define SUBMISSION
#define BRUTE_FORCE
#ifdef SUBMISSION
#undef BRUTE_FORCE
#define NDEBUG
#endif
#include <iostream>
#include <vector>
#include <limits>

#include <cassert>

#include <sys/time.h> // TODO - this is only for random testcase generation.  Remove it when you don't need new random testcases!

using namespace std;

template <typename T>
T read()
{
    T toRead;
    cin >> toRead;
    assert(cin);
    return toRead;
}

void solutionBruteForceAux(const int N, const int P, const vector<int64_t>& a, int index, int cupNum, int64_t minCostSoFar, int64_t& best)
{
    if (index == N)
    {
        if (cupNum == P)
        {
            best = min(best, minCostSoFar);
        }
        return;
    }

    solutionBruteForceAux(N, P, a, index + 1, cupNum, minCostSoFar + cupNum * a[index], best);
    if (cupNum <= index)
        solutionBruteForceAux(N, P, a, index + 1, cupNum + 1, minCostSoFar + (cupNum + 1) * a[index], best);
}

int64_t solveBruteForce(int N, int P, const vector<int64_t>& a)
{
    int64_t result = numeric_limits<int64_t>::max();

    solutionBruteForceAux(N, P, a, 0, 1, 0, result);
    
    return result;
}

int64_t solveOptimised(int N, int P, const vector<int64_t>& a)
{
    vector<vector<int64_t>> minWithFirstNInPCups(N + 1, vector<int64_t>(P + 1, numeric_limits<int64_t>::max()));

    for (int i = 0; i <= N; i++)
    {
        minWithFirstNInPCups[i][0] = 0;
    }
    for (int i = 0; i <= P; i++)
    {
        minWithFirstNInPCups[0][i] = 0;
    }

    for (int i = 1; i <= N; i++)
    {
        for (int j = 1; j <= P; j++)
        {
            const auto addNewAToNewCup = (i >= j && (j > 1 || i == 1) ? minWithFirstNInPCups[i - 1][j - 1] + (j * a[i - 1]) : numeric_limits<int64_t>::max());
            const auto addNewAToOldCup = (i - 1 >= j ? minWithFirstNInPCups[i - 1][j] + (j * a[i - 1]) : numeric_limits<int64_t>::max());


            minWithFirstNInPCups[i][j] = min(addNewAToNewCup, addNewAToOldCup);

        }
    }

    return minWithFirstNInPCups[N][P];
}

int main(int argc, char* argv[])
{
    ios::sync_with_stdio(false);
    if (argc == 2 && string(argv[1]) == "--test")
    {
        struct timeval time;
        gettimeofday(&time,NULL);
        srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

        int N = rand() % 5 + 1;
        int P = rand() % 5 + 1;
        const int maxAbsA = rand() % 100 + 1;

        if (N < P)
            swap(P, N);

        cout << N << " " << P << endl;

        for (int i = 0; i < N; i++)
        {
            int a = rand() % maxAbsA;
            if (rand() % 2 == 0)
                a = -a;
            cout << a << " ";
        }
        cout << endl;

        return 0;
    }
    
    const int N = read<int>();
    const int P = read<int>();

    vector<int64_t> a(N);
    for (int i = 0; i < N; i++)
    {
        a[i] = read<int>();
    }


    
#ifdef BRUTE_FORCE
#if 1
    const auto solutionBruteForce = solveBruteForce(N, P, a);
    cout << "solutionBruteForce: " << solutionBruteForce << endl;
#endif
#if 1
    const auto solutionOptimised = solveOptimised(N, P, a);
    cout << "solutionOptimised:  " << solutionOptimised << endl;
    
    assert(solutionOptimised == solutionBruteForce);
#endif
#else
    const auto solutionOptimised = solveOptimised();
    cout << solutionOptimised << endl;
#endif

    assert(cin);
}

Edit:

Huh - just looked through the AC solutions, and a surprisingly large amount used the DP approach, rather than the one in the Editorial. Don’t feel so bad, now :slight_smile:

1 Like

Well, let’s make sure what I’ve written isn’t total garbage, first :slight_smile: If I ever get some of my own Problems accepted, I’ll do the Editorials for them as practice :slight_smile:

3 Likes

This problem is 100% copy of a contest from 6 months ago on codeforces. There is a very nice editorial for it as well. I knew the solution before looking at the qn lol.

3 Likes

Provide me link to solution of codeforces …rokda denga…:last_quarter_moon_with_face::full_moon_with_face:

That is the benefit of practice. Same qns appear here and there​:joy::grin:

It simply means solve as more as possible, u never know which problem comes which u already did.

1 Like

The place from where the question was taken:-

(Even Sample Test Cases Match) .
Nice Job Setter :stuck_out_tongue:

2 Likes

Yeah, setter should take on the responsibility of not asking existing questions or atleast not copying them 100% (I know I am speaking way out of my potential (aukat as they in Hindi.) being an extreme noob). In my very humble opinion it defeats the purpose of contest and gives an unfair advantage to those who have already solved the problem.

3 Likes

But this question is easy-medium. I mean searching for it, or deriving its solution would take same time. You can never trust external contests.

1 Like

Even the last question is standard and matches some places, but we cant complain as every qn cant be fully new naa…

2 Likes

The reason why external contests are unrated.

2 Likes

I don’t think good setters will ever ask repeated problems. I don’t think one would ever find repeated questions in Long, Lunchtime or Cook-Off. But I agree solving more problems help, I remember reading one answer of Bohdan Pryschenko(I_love_tanya_romanova) that he is able to solve hard problems because he has solved a lot many of them and he is often able to find patterns that he saw in problems he had solved earlier. He says he has solved over 8000 problems!:frowning:

1 Like

Yup…But I like both rated as well as unrated .
The next contest I am allowed to participate in is Oct-Long. Exams in Sep.:disappointed_relieved:

Same here, for me Oct-CookOff because I don’t do Long now.

Long is often too much hard work , 10 days investment and less returns…
Though last 4-5 problems of div-1 are too good.

1 Like

I agree with you all the way, but to let you know for the fact it is literally not a 100% match to the codeforces problem.(See what the question is asking):sweat_smile:

for a 1st std kid, it is not 100% matching, but for any guy above 8th std and common sense, it is 100% matching, the original question asks for maximum and this asks for minimum , any one with basic coding knowledge and whoever has solved the question will know what one small change you have to do in the code to get minimum instead of maximum and vice-versa.

Its like a question came in college exam like this:-

Sort the numbers in descending order

But you say, that this question is from out of syllabus, doesn’t match what was taught in colleges or textbook, because they only taught you to sort in ascending order, so this is different question. Lol.

2 Likes