4

The Goal

I'm solving this problem:

Little Girl and Maximum Sum

The little girl loves the problems on array queries very much.

One day she came across a rather well-known problem: you've got an array of n elements (the elements of the array are indexed starting from 1); also, there are q queries, each one is defined by a pair of integers li, ri (1 ≤ li ≤ ri ≤ n). You need to find for each query the sum of elements of the array with indexes from li to ri, inclusive.

The little girl found the problem rather boring. She decided to reorder the array elements before replying to the queries in a way that makes the sum of query replies maximum possible. Your task is to find the value of this maximum sum.

Input The first line contains two space-separated integers n (1 ≤ n ≤ 2·105) and q (1 ≤ q ≤ 2·105) — the number of elements in the array and the number of queries, correspondingly.

The next line contains n space-separated integers ai (1 ≤ ai ≤ 2·105) — the array elements.

Each of the following q lines contains two space-separated integers li and ri (1 ≤ li ≤ ri ≤ n) — the i-th query.

Output In a single line print a single integer — the maximum sum of query replies after the array elements are reordered.

With test 7 (see the test results in the end of the question), the input is an array of size 200,000 with 200,000 queries (which have r and l values).

The input looks like this:

200000 200000
189622 189286 194361 184457 182376 183471 197548 184736 195806 ... 200,000 integers

188738 290041
33738 90041
122738 390041
... 200,000 line

You can download a sample input file, or you can create your own sample input. It wouldn't matter the numbers itself.


The Problem

I need to read 600,000 input lines without exceeding 2 seconds of execution time. The problem is, it doesn't even read the first 200,000 input in 2 seconds.

How can I speed up my code to read all 600,000 input within 2 seconds?


The Code

Here is my first attempt:

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int q = scanner.nextInt();
        int[] array = new int[n];
        for (int i=0; i<n; i++) {
            array[i] = scanner.nextInt();
        }
        int[][] qArray = new int[q][2];
        for (int i=0; i<q; i++) {
            qArray[i][0] = scanner.nextInt();
            qArray[i][1] = scanner.nextInt();
        }

        int[] index = new int[n];
        Arrays.sort(array);
        for (int i=0; i<q; i++) {
            for (int j = qArray[i][0]-1; j<qArray[i][1]; j++) {
                index[j]++;
            }
        }
        Arrays.sort(index);
        long sum =0;
        for (int i = 0; i<n; i++) {
            sum += index[i]*array[i];
        }
        System.out.println(sum);
    }
}

Here is my second attempt:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            String input = bufferedReader.readLine();
            String[] SplitInput = input.split(" ");
            int n = Integer.parseInt(SplitInput[0]);
            int q = Integer.parseInt(SplitInput[1]);

            String input2 = bufferedReader.readLine();

            int[][] qArray = new int[q][2];
            for (int i=0; i<q; i++) {
                input = bufferedReader.readLine();
                SplitInput = input.split(" ");
                qArray[i][0] = Integer.parseInt(SplitInput[0]);
                qArray[i][1] = Integer.parseInt(SplitInput[1]);
            }

            String[] SplitInput2 = input2.split(" ");
            int[] array = new int[n];
            for(int i=0; i<n; i++){
                array[i] = Integer.parseInt(SplitInput2[i]);
            }

            int[] index = new int[n];
            Arrays.sort(array);
            for (int i=0; i<q; i++) {
                for (int j = qArray[i][0]-1; j<qArray[i][1]; j++) {
                    index[j]++;
                }
            }
            Arrays.sort(index);
            long sum = 0;
            for (int i=0; i<n; i++) {
                sum += index[i]*array[i];
            }
            System.out.println(sum);
        }
        catch (NumberFormatException ex) {
            System.out.println("Not a number !");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Test Results

Attempt 1

7 Time: 2000 ms, memory: 20612 KB Verdict: TIME_LIMIT_EXCEEDED

Attempt 2

7 Time: 2000 ms, memory: 41340 KB Verdict: TIME_LIMIT_EXCEEDED

You can view my full test results here and here. Again, the problem is on Test 7.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Robert
  • 2,342
  • 2
  • 24
  • 41
  • 1
    So what is the question, exactly? Have you run the code through a profiler to see what the timings are? – OldProgrammer Apr 09 '16 at 19:50
  • @OldProgrammer this site is taking the code and test it , mine is not accepted – Robert Apr 09 '16 at 19:52
  • updated the post with links to the test results – Robert Apr 09 '16 at 19:54
  • It's 2 seconds per test input, not 2 seconds total. Since the test input you fail on consists of 6 digit integers, check to see if you have an integer overflow somewhere. – Gilbert Le Blanc Apr 09 '16 at 19:57
  • if there are an overflow it would say `run time error` and not `Time limit exceeded` – Robert Apr 09 '16 at 19:59
  • There I go to the trouble of actually running your code, only to find you introduced a bug when posting it ... (in your second example, you should not be doing `input = bufferedReader.readLine();` in your for-loop) – meriton Apr 09 '16 at 20:23
  • @meriton then how should i read the inputs ? – Robert Apr 09 '16 at 21:04
  • @meriton have you read the problem ?? and saw the input examples ?? , if there are a problem the site wouldn't accept my answers for the first 6 samples – Robert Apr 09 '16 at 21:12
  • @meriton you have killed the question by downvoting it -_- , now no one would look at it , even i'm wrong you should edit and correct the question not downvote it – Robert Apr 09 '16 at 21:13
  • The question has not been killed. If someone could upload the actual failure test-file then we can try to recreate. Calling readLine() 20K times is very inefficient, however, 2 seconds is also quite a long time. – Andrew Regan Apr 09 '16 at 22:40
  • I'd also suggest (a) do something about creating two separate 'n'-sized arrays, and (b) not using Arrays.sort: at all, or at least not until you have proven it helps your algorithm. You really shouldn't be anywhere near 2 secs for this problem. – Andrew Regan Apr 09 '16 at 23:44
  • 2
    You are asking us to debug a problem on a platform we do not have access to, on an input file we do not have access to, with code that contains obvious bugs. Now that you mention it, this question indeed deserves to be killed. – meriton Apr 10 '16 at 00:25
  • @meriton first the site is free anyone can go register and test it , second i gave you access to the test results , third where are the "obvious bugs" that the question deserve to be killed because of it ? – Robert Apr 10 '16 at 17:26
  • @AndrewRegan what do you mean by `upload the actual failure test-file` – Robert Apr 10 '16 at 17:28
  • @AndrewRegan `Calling readLine() 20K times is very inefficient` that's exactly what i mean i need efficient way to read the inputs ,that's all – Robert Apr 10 '16 at 17:33
  • The input file that caused the failure - so that I/others can recreate. – Andrew Regan Apr 10 '16 at 17:35
  • 2
    i don't have access to the full inputs but the first line is `200000 200000` the second line is 200,000 integers separated by space , and then 200,000 line each have two integers separated by space – Robert Apr 10 '16 at 17:40
  • i edit the question and make the input clearer – Robert Apr 10 '16 at 17:50
  • @meriton can you please tell me where the bugs are ? and what's the correct answer – Robert Apr 11 '16 at 18:10
  • "How to reopen the question"? I would recommend reading the *reason* the question was closed, and look at the links attached thereof. Once your question complies with the site guidelines, it can be reopened. – CodeMouse92 Apr 12 '16 at 18:09
  • @JasonMc92 **Questions seeking debugging help must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce**, the complete question is there and the desired behavior is to work in less than 2 seconds with the input `200000 200000` , and i included my first and second attempts, so i don't know what exactly wrong with the question that's why i asked what should i do , i want a _**clear**_ answer to what should i do to reopen the question ? – Robert Apr 12 '16 at 21:44
  • 2
    Ahhh, I see what you were asking now. I think most people (including me at first) couldn't quite distinguish what you were asking in the midst of all of that. I edited your question to make it much easier to read, and restated your question a second time (in bold). I've also nominated you for reopen. I hope you find your answer! In the interim, I do recommend running your code through a code profiler on your machine. That will help you narrow in on the performance bottleneck. – CodeMouse92 Apr 12 '16 at 21:55
  • You use slow techniques all along. IOs are slow yet you process IO then code then IO then code, etc. That just slows everything down. Instead, store all the IO at once in a String (or a String array, but nothing else), then process them together. You might use more memory but I think you're still in the limit of 256 MB (200000 ints as text = 10 * 200000 bytes = 2 MB ; as ints = 4 * 200000 bytes). Second, you use regexes (`split` uses regexes internally). Regexes are slow. Split manually, it'll be faster. – Olivier Grégoire Apr 12 '16 at 22:24
  • @JasonMc92 thanks for your help – Robert Apr 13 '16 at 07:26
  • @OlivierGrégoire how can i split strings manually ? do you mean for loop till i find space then take the previous numbers and convert them to integer ? , what about if i used the regex with the lazy mode , can i do that with java and if yes can it make the things faster ? – Robert Apr 13 '16 at 07:30
  • 1
    I can't answer, so I'll try in comments in the meanwhile. The data to properly test is clearly missing. I created my own random data set, but it fails miserably. On my machine, the smaller tests using my implementation last roughly less than 1 ms (clocked with `System.nanotime()`), while yours are consistently above 2 ms. So I believe I can help. But in comments is really annoying. I hope your question gets reopened soon. I have something that can help you, but I just can't properly answer... :( – Olivier Grégoire Apr 13 '16 at 12:07
  • 1
    Anyways, here's my split method. `static int[] split(String s,int[]a){int n=0,aIndex=0;for(int sIndex=0,sLength=s.length();sIndex – Olivier Grégoire Apr 13 '16 at 12:15
  • @OlivierGrégoire why are you returning the value , the elements values in the array should be changed , so return it again will take double the memory for nothing , or am i wrong ? – Robert Apr 13 '16 at 15:09
  • `static void split(String s, int[] a) { int n = 0, aIndex = 0; for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) { char c = s.charAt(sIndex); if (c == ' ') { a[aIndex++] = n; n = 0; } else if ('0' <= c && c <= '9') { n = n * 10 + (c - '0'); } } a[aIndex] = n; }` – Robert Apr 13 '16 at 15:12
  • the time decreased to the half indeed – Robert Apr 13 '16 at 15:13
  • 3
    Sadly, people on http://chat.stackoverflow.com suggest you post this on [CodeReview](http://codereview.stackexchange.com/) instead of StackOverflow – Olivier Grégoire Apr 13 '16 at 15:52
  • @OlivierGrégoire how can i upload a file to SO , i created a sample input but it's 3.68 MB so i want to upload it as file – Robert Apr 13 '16 at 16:08
  • we need just 1 vote to reopen the question i hope someone do – Robert Apr 13 '16 at 16:09
  • the time now is 20 seconds to read the 600,000 inputs using the first way,19 for the second way and 18 for your way , it does improve the performance with the short strings but not with the long – Robert Apr 13 '16 at 16:30
  • can we read the inputs with the line break and split them manually too ? – Robert Apr 13 '16 at 16:31
  • @robert Put the file on pastebin.com or as a gist on gist.github.com and provide the link. – Olivier Grégoire Apr 13 '16 at 16:56
  • https://www.dropbox.com/s/02qa6zi54oje0ef/input.txt?dl=0 – Robert Apr 13 '16 at 17:41
  • 1
    @OlivierGrégoire (and OP), the question is now reopened. You can go ahead and post that answer if you like. – CodeMouse92 Apr 14 '16 at 00:52
  • is that ` (1 ≤ n ≤ 2^105)` or ` (1 ≤ n ≤ 2105)`? – eckes Apr 14 '16 at 19:50
  • 1
    @eckes it's `1 ≤ n ≤ 2*10^5` or `1 ≤ n ≤ 200,000` – Robert Apr 14 '16 at 19:57

1 Answers1

6

Disclaimer, I said I was able to help you, I am, but I can't solve it for you. I haven't been able to limit it under 2 seconds, because I didn't properly understand the question itself. I understand what your algorithm does, technically, but I have issues to understand it conceptually, making me unable to find the big optimization. I found the big optimization. See bottom of the answer.

Remarks: I've seen your results on the test page for the smaller tests and there is absolutely no reason why your first tests last 200+ ms. I just don't understand that. They're all running consistently under 2 ms on my computer (using the Java internal System.nanotime() method). I believe the test include the startup of the JVM. If that's actually the case, may I suggest that you switch to a more optimized language like C or C++? Meaning that the test is somewhat rigged against interpreted languages.

Algorithm

The first problem is your algorithm itself. It's slow: You're actually iterating through 200,000 × x ints (which is a high value on average, from your file). In the worst case, you're iterating through 200,000 × 200,000 = 40,000,000,000 ints. No wonder you're having times around the 20 seconds.

That's way too much. Ideally, you should be able to reduce that double loop using optimization like using a map. You have a great amount of memory at your disposal (256 MB), use it. You already do it; do it more.

The big optimization lies somewhere here. I believe that instead of incrementing index by index you should find another optimization by skipping this index mechanism and using a better one. I believe that it's the reason why the question exists: find that algorithm and no other. I dislike that, but I don't judge it.

Reading data

I tested reading data through the input and your method is slow. I blame your use of Scanner.

I suggest you use this construction and this split method which run in < 100 ms total on my computer with your big test file (I insist... my computer is not your computer and our computers are not the computer your test is evaluated on). I believe that it can be reduced further, but I'd say it's good enough. It's not the big optimization we're looking for, but it's the second, I believe.

try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
  int[] counts = split(reader.readLine(), new int[2]);
  int n = c[0], q = c[1];
  int[] array = split(reader.readLine(), new int[n]);
  int[][] queries = new int[q][]; // Note, no size in the second part of the array creation.
  for (int i = 0; i < q; i++) {
    queries[i] = split(reader.readLine(), new int[2]);
  }
  ...
}

With the appropriate split method which is optimized for your use case:

static int[] split(String s, int[] a) {
  int n = 0, aIndex = 0;
  for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
    char c = s.charAt(sIndex);
    if (c == ' ') { // Separator
      a[aIndex++] = n;
      n = 0;
    } else if ('0' <= c && c <= '9') { // Number
      n = n * 10 + (c - '0'); // Add a digit to the current number
    }
  }
  a[aIndex] = n;
  return a;
}

Small optimizations

Conceptually, you have the following code:

for (int i = 0; i < q; i++) {
  // Fill qArray
}

for (int i = 0; i < q; i++) {
  // Work with index.
}

These two loops could be merged, further even eliminating the need for your qArray. You read data, and you process it directly. This is not really important if the loops are next to each other, but in between you're sorting stuff in an array in your first attempt, and you're both sorting an array and parsing the input in your second attempt. This makes your data go far from the CPU cache on one hand, but you're processing I/O on the other one. I don't know which one is better.

You have a bug in your code

I tried to rethink the problem and found a solution, but your answer was never the same as mine. And I actually found a bug in your code. I couldn't get the same result as you with your file.

In your final loop, the sum-loop, you store everything in a long, but it's actually possible to get an int overflow. So you should make your sum like this:

sum += (long)(index[i]) * array[i];

Found it!

Regarding your code, as I said, you have a problem because you potentially get more than 40 billion instructions. I could flatten your the solution with what you can see below. And I consistently hit the 500 ms now.

public static void main(String[] args) throws IOException {
  long nanos = System.nanoTime();
  myMain();
  nanos = System.nanoTime() - nanos;
  System.out.printf("Time: %sms%n", MILLISECONDS.convert(nanos, NANOSECONDS));
}

static void myMain() throws IOException {
  try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
    int[] counts = split(reader.readLine(), new int[2]);
    int n = counts[0], q = counts[1];
    int[] array = split(reader.readLine(), new int[n]);
    int[] indices = new int[n];
    for (int i = 0; i < q; i++) {
      int[] query = split(reader.readLine(), new int[2]);
      indices[query[0] - 1]++;
      if (query[1] < n) {
        indices[query[1]]--;
      }
    }
    for (int i = 1; i < n; i++) {
      indices[i] += indices[i - 1];
    }
    sort(array, 200_000);
    sort(indices, 200_000);
    long sum = 0;
    for (int i = 0; i < n; i++) {
      sum += (long)array[i] * indices[i];
    }
    System.out.println(sum);
  }
}

static void sort(int[] array, int n) {
  int[] counts = new int[n+1];
  for (int element: array) {
    counts[element]++;
  }
  int current = 0;
  for (int i = 0; i < counts.length; i++) {
    Arrays.fill(array, current, current + counts[i], i);
    current += counts[i];
  }
}

static int[] split(String s, int[] a) {
  int n = 0, aIndex = 0;
  for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) {
    char c = s.charAt(sIndex);
    if (c == ' ') {
      a[aIndex++] = n;
      n = 0;
    } else if ('0' <= c && c <= '9') {
      n = n * 10 + (c - '0');
    }
  }
  a[aIndex] = n;
  return a;
}

Enjoy!

If you have any question regarding this optimization, don't hesitate ;)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • 1
    i appreciate all the things you did so far , i will merge all these ideas together to one program and when i find the best solution i will post it here – Robert Apr 14 '16 at 10:06
  • @robert I like some puzzles, so I had to get it. Please find my improvements in my edited answer. – Olivier Grégoire Apr 14 '16 at 19:39
  • btw `printf` is slower than `print` or `println` :) ,i have tried it with very big output the other day and found that `print` is very fast than `printf` – Robert Apr 14 '16 at 19:52
  • @OlivierGrégoire you can reuse the `query` (saves a `new int[2]`) inside the loop as well. Might not make much difference because of escape analysis but since it is a straight forward change (and your `split` method already gets the input) it would not make the code uglier. – eckes Apr 14 '16 at 19:54
  • Yeah, I know I can make it better, but I didn't put any more effort in optimizing when I beat the 2 seconds limit consistently. Sorry for that, but of course, if you want to make it as fast as possible, there are always improvements like those both of you mention. – Olivier Grégoire Apr 14 '16 at 20:02
  • here are the result of your code http://codeforces.com/contest/276/submission/17319709 thanks alot for your help , i didn't think we can make it that far – Robert Apr 14 '16 at 20:05