OKLAMA - Editorial

PROBLEM LINK:

Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4

Author: etherinmatic
Tester: kaori1
Editorialist: iceknight1093

DIFFICULTY:

TBD

PREREQUISITES:

Sorting, sets/priority queues

PROBLEM:

You are given an array of length N, where N is odd. You can do the following:

  • Choose three distinct indices i, j, k.
  • Delete A_i, A_j, A_k from the array.
  • Insert A_i + A_j - A_k into the array.

In the end, a single element will remain. Your aim is to maximize this final value.

Answer Q queries, each asking for the answer when a prefix of A is considered.

EXPLANATION:

Letā€™s first solve for a single array A, forgetting multiple queries.

No matter how the operations are performed, the final value is going to be a combination of the initial A_i values - just that each of them will either have a +1 or -1 coefficient (i.e, be added or subtracted).
The ideal situation is, of course, when every positive number receives a +1 coefficient and every negative number receives -1.
However, that isnā€™t always possible.

For example, analyzing some small cases:

  • For N = 3, we have three values (say a, b, c).
    The operation lets us end up with the sum of two of them, minus the third.
  • For N = 5, we have five values (say a, b, c, d, e).
    Letā€™s say that initially, everything has the + sign.
    After the first operation, four values will have the + sign and one will have -.
    After the second operation,
    • If the newly combined element is one of the added ones, weā€™ll have three plus signs and two minuses.
    • If the newly combined element is subtracted, weā€™ll still have three plus signs and two minuses.
  • In particular, you may observe that for N = 5, itā€™s possible to obtain any combination of 3 pluses and two minuses.

The last part seems suspicious: and indeed, we can generalize it.

Claim: For a given odd N, no matter how the operations are performed, the final element will be the sum of \frac{N+1}{2} of the elements, minus the sum of the other \frac{N-1}{2} elements.
Further, any set of elements can be chosen as the ones being added.

Proof

There are two things to be proved here: first that the final element will be the sum of \frac{N+1}{2} of the elements, minus the sum of the other \frac{N-1}{2} elements, and second that any such configuration is achievable.

The first claim can be proved using induction.
For N = 1 itā€™s trivially true.
For N\gt 1, perform any move initially; leaving you with one ā€˜combinedā€™ element thatā€™s two pluses and one minus.
Now, by the inductive hypothesis, performing moves on the remaining array of size N-2 will leave us with something thatā€™s \frac{N-1}{2} pluses and \frac{N-3}{2} minuses.
Note that:

  • If the ā€˜combinedā€™ element is a plus in the final configuration, it then contributes to two pluses and one minus.
    Apart from it, the number of pluses and minuses will be equal, so the hypothesis holds.
  • If the combined element is a minus in the final configuration, the hypothesis still holds: the negative part of it will become a plus in the final configuration, while the positive parts become minuses.

As for the second claim, thereā€™s a pretty simple construction that achieves it.
Every move, choose two elements that are pluses and one thatā€™s a minus, and perform the operation using them - the new element should be designated a plus.

This characterization gives us a direct solution: \frac{N+1}{2} elements are added and the rest are subtracted, so of course to maximize the sum, the largest \frac{N+1}{2} elements should be added and the rest should be subtracted.
This can easily be found in \mathcal{O}(N\log N) time by just sorting the array.


Now, we know how to solve the task in \mathcal{O}(N\log N) time for a single query.
This solves the first subtask, but not the second.

To solve the second subtask, weā€™ll instead just precompute the answers for each odd-length prefix, and look them up later.

Suppose we know the answer for the prefix of length i, meaning we know the split of the first i elements into large and small.
When A_{i+1} and A_{i+2} are added, not much changes really.

Specifically, suppose you store the ā€œlargeā€ elements in a list L, and the ā€œsmallā€ elements in another list S.
Note that L and S have the property that |L|-|S| = 1, and also \min(L)\geq \max(S).
These lists can then easily be updated as follows:

  • Insert \min(A_{i+1}, A_{i+2}) into S, and \max(A_{i+1}, A_{i+2}) into L.
    This keeps the sizes balanced.
  • Now, while \min(L) \lt \max(S), extract the minimum of L and the maximum of S, and put them into the other list.
  • Make sure to maintain the sums of L and S while performing these operations, since in the end thatā€™s what we really care about.

With a structure that supports quick insertion, deletion, and lookup of its minimum/maximum element (such as a priority queue or multiset), this takes \mathcal{O}(\log N) time per index, for \mathcal{O}(N\log N) time overall.
(Note that weā€™ll only move a constant number of elements between lists at any index).

Queries can be answered in constant time after this is done.

This trick of maintaining two lists for large/small elements can also be seen in, for example, the streaming median problem.

TIME COMPLEXITY:

\mathcal{O}(N\log N + Q) per testcase.

CODE:

Tester's code (C++)
#include<bits/stdc++.h>

using namespace std;

#define all(x) begin(x), end(x)
#define sz(x) static_cast<int>((x).size())
#define int long long
typedef long long ll;




signed main() {

        ios::sync_with_stdio(0);
        cin.tie(0);

        int t;
        cin >> t;

        while (t--) {

                int n, q;
                cin >> n >> q;
                int a[n];
                for (auto &x : a) cin >> x;
                int ans[n];
                multiset<int> s1, s2;       
                int sum1 = 0, sum2 = 0;

                int i = -1;
                for (auto u : a) {
                        i++;
                        s1.insert(u);
                        sum1 += u;
                        while (sz(s1) >= sz(s2)) {
                                int x = *s1.rbegin();
                                s1.erase(s1.find(x));
                                s2.insert(x);
                                sum1 -= x;
                                sum2 += x;
                        }
                        while (sz(s1) && sz(s2) && *s1.rbegin() > *s2.begin()) {
                                int x1 = *s1.rbegin();
                                int x2 = *s2.begin();
                                s1.erase(s1.find(x1));
                                s2.erase(s2.find(x2));
                                s1.insert(x2);
                                s2.insert(x1);
                                sum1 += x2 - x1;
                                sum2 += x1 - x2;
                        }
                        ans[i] = sum2 - sum1;
                }
                
                while (q--) {
                        int j;
                        cin >> j;
                        j--;
                        cout << ans[j] << " ";
                }
                cout << "\n";

        }
        
        
}
Editorialist's code (Python)
from heapq import heappop, heappush

for _ in range(int(input())):
    n, q = map(int, input().split())
    a = list(map(int, input().split()))
    ans = [0]*n
    small, large = [], []
    smallsm, largesm = 0, 0
    ans[0] = a[0]
    largesm = a[0]
    heappush(large, a[0])
    for i in range(2, n, 2):
        x, y = min(a[i], a[i-1]), max(a[i], a[i-1])
        heappush(small, -x)
        smallsm += x
        heappush(large, y)
        largesm += y

        while -small[0] > large[0]:
            x, y = heappop(small), heappop(large)
            x *= -1
            smallsm -= x
            largesm -= y

            heappush(small, -y)
            heappush(large, x)
            smallsm += y
            largesm += x
        ans[i] = largesm - smallsm
    
    answers = [0]*q
    queries = list(map(int, input().split()))
    for i in range(q):
        x = queries[i]
        answers[i] = ans[x-1]
    print(*answers)

3 Likes

these submission ids have almost same code and still they are not yet banned
1060348136 1060347443 1060347416 1060347237

4 Likes

During every Codechef contest all codes get uploaded in this telegram group and all copy same code from here. Telegram: Contact @codechef_answers.
Massive cheating has been done in the problem :- 3out1in; 3out1in Practice Coding Problem - CodeChef.
@admin

85% of codes are exactly of the same structure as this:-

void solve()
{
int n,q; cin >> n>>q;

vector<int> a(n); 
for (auto &x : a) cin >> x; 
 
multiset<int> hi, lo; 
int sum_hi = 0, sum_lo = 0; 
vector<int>final; 
for (int i = 0; i < n; ++i){ 
    if (i & 1){ 
        if (a[i] > *hi.begin()){ 
            sum_hi += a[i] - *hi.begin(); 
            sum_lo += *hi.begin(); 
            lo.insert(*hi.begin()); 
            hi.insert(a[i]); 
            hi.erase(hi.begin()); 
        } else{ 
            lo.insert(a[i]); 
            sum_lo += a[i]; 
        } 
    } else { 
        if (lo.empty()){ 
            hi.insert(a[i]); 
            sum_hi += a[i]; 
        } else if (a[i] < *lo.rbegin()){ 
            sum_hi += *lo.rbegin(); 
            sum_lo += a[i] - *lo.rbegin(); 
            hi.insert(*lo.rbegin()); 
            lo.insert(a[i]); 
            lo.erase(lo.find(*lo.rbegin())); 
        } else{ 
            hi.insert(a[i]); 
            sum_hi += a[i]; 
        } 
         
    } 
    final.push_back(sum_hi-sum_lo); 
} 
for(int i=0;i<q;i++){ 
   int x; 
   cin>>x; 
   cout<<final[x-1]<<" "; 
} 
cout<<endl; 

}

Submission Link -

CodeChef: Practical coding for everyone.
CodeChef: Practical coding for everyone.
CodeChef: Practical coding for everyone
CodeChef: Practical coding for everyone
CodeChef: Practical coding for everyone

2 Likes

How do you find these?

Can someone help me I tried this code but gave Wrong Answer maybe I did some mistake

#include<bits/stdc++.h>
using namespace std;
void fun(){
int n,m;
cin>>n>>m;
vectorarr(n);
vectorquery(m);
vector ans(n);
for(int i=0;i<n;i++){
cin>>arr[i];
}
for(int j=0; j<m;j++){
cin>>query[j];
}
priority_queuemaxHeap;
priority_queue<int, vector, greater>minHeap;
int sum=0;
for(int i=0; i<n;i++){
if(i==0){
sum+=arr[i];
minHeap.push(arr[i]);
ans[i]= sum;
continue;
}
else if(maxHeap.size()==0){
if(minHeap.top() < arr[i]){
int value= minHeap.top();
minHeap.pop();
sum-= value;
sum+= arr[i];
sum-= value;
minHeap.push(arr[i]);
maxHeap.push(value);
}
else{
maxHeap.push(arr[i]);
sum-= arr[i];
}
ans[i]=sum;
continue;
}
else if(i%2==0){
if(arr[i] > maxHeap.top()){
sum+=arr[i];
minHeap.push(arr[i]);
}
else{
int ele = maxHeap.top();
maxHeap.pop();
sum+= 2ele;
minHeap.push(ele);
maxHeap.push(arr[i]);
sum-= arr[i];
}
ans[i]= sum;
continue;
}
else{
if(arr[i] < minHeap.top()){
sum-= arr[i];
maxHeap.push(arr[i]);
}
else{
int ele = minHeap.top();
minHeap.pop();
sum-=2
ele;
maxHeap.push(ele);
minHeap.push(arr[i]);
sum+=arr[i];
}
ans[i]= sum;
}
}
for(int i=0; i<m;i++){
cout<<ans[query[i]-1]<<" ";
}
cout<<endl;
}
int main(){
int tn;
cin>>tn;
while(tnā€“){
fun();
}
return 0;
}

could anyone provide first test case its seems thereā€™s some edge case there! hereā€™s my code CodeChef: Practical coding for everyone

Can somebody tell whatā€™s wrong in this approach:
include <bits/stdc++.h>
include
using namespace std;
int main(){
int t;
cin>>t;
while(tā€“){
int n,q;
cin>>n>>q;
long long a[n];
long long sum[n];
long long min1[n];
priority_queue <long long, vector, greater> gq;
int query[q];
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<q;i++){
cin>>query[i];
}
sum[0]=a[0];
for(int i=1;i<n;i++){
sum[i]=sum[i-1]+a[i];
}
gq.push(a[0]);
min1[0]=0;
for(int i=1;i<n;i++){
gq.push(a[i]);
if(i%2==1){
min1[i]=min1[i-1];
}
else{
min1[i]=min1[i-2]+gq.top();
gq.pop();
// cout<<min1[i]<<" ā€œ<<i<<endl;
}
}
for(int i=0;i<q;i++){
// cout<<sum[query[i]-1]<<ā€ "<<query[i]-1<<endl;
cout<<(sum[query[i]-1]-2*min1[query[i]-1])<<endl;
}
cout<<endl;
}
}

It has been a great editorial but I am confused regarding the approach I am using, why does it get TLE.

My code Logic(ll is long long) entire code:-

void test() {
  ll n, q;
  cin >> n >> q;
  vll a(n);
  read(a);

  multiset<ll> tmp;

  vector<ll> ans(
      n + 1, 0); // k will always be odd hence only odd indices will have answer
  for (auto ele : a) {
    tmp.insert(ele);
    if (tmp.size() % 2 == 0)
      continue;

    ll till = tmp.size() / 2;

    ll val = 0;
    auto itf = tmp.begin();
    auto itb = tmp.rbegin();
    while (till--) {
      val += (*itb - *itf);
      itb++;
      itf++;
    }
    val += *itb;
    ans[tmp.size()] = val;
  }
  while (q--) {
    ll k;
    cin >> k;
    cout << ans[k] << " ";
  }
  cout << endl;
}

Above code gets TLE on Task 2 subtask 1.

Now another approach which I found during contest based on prefix sums which seems to me more time complex code does perform much better.

My code logic entirecode:-

 ll n, q;
  cin >> n >> q;
  vll a(n);
  read(a);

  while (q--) {
    ll k;
    cin >> k;
    vll tmp(k), pref(k + 1, 0);
    fori(0, k) { tmp[i] = a[i]; }
    sort(all(tmp), greater<ll>());
    // pref sum of sorted array
    fori(1, k + 1) { pref[i] = pref[i - 1] + tmp[i - 1]; }

    ll br = (k + 1) / 2;
    ll tosub = pref[k] - pref[br];
    ll toadd = pref[br];
    ll ans = toadd - tosub;
    cout << ans << " ";
  }
  cout << endl;

This only gets TLE in task 5 subtask 2.

My doubt is the editorial code runs fine but when I modify it a little why does the performance drop for my approach where I use multiset. Where am I going wrong?

Guidance from people who could solve it is appreciated. Pardon me for any logic which I might have slipped.

WRONG ANSWER FOR HIDDEN TESTCASE

I am using 2 multisets in my solution:
Neg : All the values that must be subtracted to get the answer
Rem: Rest of positive values

I am inserting first element in rem and then increasing i by 2 with every iteration as the value of k is odd

I precompute the answer in vector ā€œansā€ and then use it to answer each query in O(1)

include
include
include
define int long long int
define input(arr, n)
for (int i = 0; i < n; i++)
cin >> arr[i];
using namespace std;
int32_t main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (tā€“){
int n,q;
cin>>n>>q;
vectorarr(n);
input(arr,n);
multiset neg;
multiset rem;
vectorans;
rem.insert(arr[0]);
int val=arr[0];
ans.push_back(val);
for(int i=1;i<n;i+=2){
int x=min(arr[i],arr[i+1]);
int y=max(arr[i],arr[i+1]);
int r=rem.begin();
if(neg.size()==0){
if(x<r){
neg.insert(x);
val-=x;
}
else{
neg.insert(r);
rem.erase(r);
rem.insert(x);
val=val + x - 2
r;
}
rem.insert(y);
val+= y;
}
else if(x<*neg.rbegin()){
int n=neg.rbegin();
neg.insert(x);
val-= x;
if(y<n){
neg.erase(n);
rem.insert(n);
neg.insert(y);
val=val + 2
n - y;
}
else{
rem.insert(y);
val+= y;
}
}
else{
int n=neg.rbegin();
rem.erase(r);
neg.insert(r);
rem.insert(x);
rem.insert(y);
val=val + x + y - 2
r;
}
ans.push_back(val);
}
while(qā€“){
int k;
cin>>k;
cout<<ans[(k-1)/2]<<" ";
}
cout<<endl;
}
return 0;
}

In your first approach, you have used while loop inside the for loop. The while loop runs n/2 times for a given n. Therefore, your time complexity is O(n^2), which is slow.

In your second solution, you are answering each query in O(k \cdot logk) and there can be n/2 such queries resulting in overall O(n \cdot k \cdot logk) solution which wonā€™t pass

1 Like

Ya I got it. Can you explain the solution in editorial time complexity too, I am maybe not getting the solution correctly I guessšŸ˜¬

I am sorry I didnā€™t get what you were trying to ask. Are you asking for an explanation of solution or an explanation for time complexity ?

1 Like

I am actually having a hard time in getting the logic of the editorial code, based on my understanding I coded the last 2 solutions but they were slow, and the editorial code is fast but why I am confused.

Can you explain me this?

Our answer to a given query is sum of all k elements - sum of smallest (n-1)/2 elements. We can compute the sum of first k elements using prefix sums in O(1). Now we need to compute the sum of (n - 1)/2 smallest elements efficiently. To do this, we maintain two priority queues of smaller and larger elements. If we have process first i elements, then priority queue of smaller elements will contain the (i-1)/2 smallest elements and the remaining larger elements will be in the other priority queue. Now to move from i to i+2, size of both priority queue should increase by 1 each in order to maintain the requirement of (k-1)/2 smaller elements. So we will look at both the element, i.e. element at i+1 and element at i+2 and adjust them such that the resulting queue size of both queue increase by 1.

You can check my submission here.

1 Like

Yeah I got it @i_am_wiz thanks for the reply. It took me quite a time to sink in the logic.

(Sorry for late viewingšŸ˜…)

1 Like