简体   繁体   中英

how to make the execution time less(ie. a faster code) for this problem

this question is from Codechef.com [if anyone is still solving this question dont look further into the post before trying yourself] and although it runs right, but i need to make it a lot faster.i am a beginner in c,c++.(i know stuff upto arrays,strings and pointers but not file handling etc).So is there a way to make this program run any faster without making it complex(its ok if its complex in algorithm). i will accept complex coding too if u mention which book u followed where it is all given :) . i currently am following Robert Lafore. Here is the program:-

There are N numbers a[0],a[1]..a[N - 1]. Initally all are 0. You have to perform two types of operations :

1) Increase the numbers between indices A and B by 1. This is represented by the command "0 AB"

2) Answer how many numbers between indices A and B are divisible by 3. This is represented by the command "1 AB".

Input :

The first line contains two integers, N and Q. Each of the next Q lines are either of the form "0 AB" or "1 AB" as mentioned above.

Output :

Output 1 line for each of the queries of the form "1 AB" containing the required answer for the corresponding query.

Sample Input :

4 7
1 0 3
0 1 2
0 1 3
1 0 0
0 0 3
1 3 3
1 0 3

Sample Output :

4
1
0
2

Constraints :

1 <= N <= 100000
1 <= Q <= 100000
0 <= A <= B <= N - 1

HERE IS MY SOLUTION:-

#include<stdio.h>

int main()
{
    unsigned int n; //amount of numbers taken
    scanf("%u",&n);

    unsigned int arr[n],q,a,b,count=0,j,i;
    short int c;
    scanf("%u",&q);//cases taken  
    for(i=0;i<n;++i)
    {
      arr[i]=0;
    }    


    for(i=0;i<q;++i)
    {
      scanf("%d%u%u",&c,&a,&b);//here a,b are A and B respectively and c is either 
                               //o or 1

      if(c==0)
      {
        for(j=a;j<=b;++j)
          arr[j]++;
      }


      if(c==1)
      {
        for(j=a;j<=b;++j)
        {
          if(arr[j]%3==0)
            count++;
        }
       printf("%u\n",count);
      }
      count=0;
    }  

}

1) Increase the numbers between indices A and B by 1. This is represented by the command "0 AB"

2) Answer how many numbers between indices A and B are divisible by 3. This is represented by the command "1 AB".

Initially numbers are 0 and thus are divisible by 3. Increment by one make the number non-divisible. Next increment - number still non-divisible. Third increment makes the number again divisible.

First optimization one can try is to not to let number grow above 2: if during increment number goes from 2 to 3, set it back to zero. Now search for the range become a simple comparison with 0. (That way array would contain instead of the number its modulo 3.)

Second optimization is to use extents instead of plain array, eg something similar to the RLE : collapse into a range all adjacent numbers with the same divisibility. Instead of numbers, the array would contain structures like that:

struct extent {
   int start; /* 0 .. N-1; extent should have at least one number */
   int end;   /* 0 .. N   */
   int n;     /* 0, 1, 2; we are only interested in the result of % */
};

Initially the array would contain single extent covering the all numbers {0, N, 0} . During increment step, a range might be split or joined with adjacent one. That representation would speed-up the counting of numbers as you would go over the array not one-by-one but in chunks. (It would still degrade to the linear search if all ranges are contain only one element.)


Another approach could be to use instead of the array three sets with indeces. Set #0 would contain all the indeces of numbers whose modulo 3 is 0, set #1 - 1, set #2 - 2. Since during increment operation, we need to do a search, instead of std::set it is better to use eg std::bitset with every bit marking the index of the number belonging to the set.

NB This way we do not keep the original numbers at all. We implicitly keep only the result of modulo 3.

During increment, we need to find which set the index belongs to, eg set #n, and move the index to the next (mod 3) set: set the bit to zero in the set n , set the bit to 1 in the set n + 1 (mod 3) . Counting numbers divisible by 3 now ends up being as simple as counting the non-zero bits in the set #0. That can be accomplished by creating a temp std::bitset as a mask with bits in range [A,B] set to one, masking with the temp set the set #0 and calling std::bitset::count() on the resulting bitset.

Two very simple optimizations:

You could really only store the value modulo 3 in the array, and not the actual value.

The increment could be done by a simple lookup table (to avoid comparisons and branches):

char increment[3] = { 1, 2 ,0 };
new_val = increment[old_val];

Testing for 3-divisibility is now comparing to 0 - which is significantly faster than integer division.

One improvement that you can make is to replace

if (c == 0) {
    //code here
}

if (c == 1) {
   // code here
}

with:

if (c == 0) {
   //...
} else if (c == 1) {
  //...
}

if you know for sure that c will always be 0 or 1, you can also replace the else if with a simple else .

What's really slowing you down is the I/O. If it's a big enough list, it might pay to malloc enough memory to hold the input and output. Then collect all the input before you enter the loop and display the output at the end.

From what I can see, your code is as optimized as it will ever be.

-Alex

Your solution seems fine I think apart from lacking any bounds checking. Maybe use an 'else' when checking 'c' or a switch, but this will save you trivial amounts of time. I don't think you'll find something as useless as this in any book.

This looks pretty performant to me. One thing I see is that you're using variable length arrays , this is OK for C (AFAIK) but illegal in C++, for C++ you will need to either use an std::vector or new up the array.

The only place I can see you improving your performance is by using Duff's Device for partial loop unrolling which I wouldn't recommend for a toy sample.

This is pretty complicated, but stay with me. I can't cite any specific place I got this from except "27 years of coding experience."

The original problem has the number line set to natural integers 0,1,2,3,4,5,6... However, we only care about numbers that are divisible by 3, so let's redefine our number line to have only three values in it: {2,3,4} and remap the number line such that:

0 => 4
1 => 2
2 => 3
3 => 4
4 => 2
5 => 3
6 => 4
.. and so on.

You'll notice that numbers that are divisible by 3 are mapped to 4 in our sequence. Why use {2,3,4}? 4 is 100 in binary, which means any element with its 3rd bit set will be divisible by 3. This is easy to test with bit operations.

Since we're using 2,3,4 as the trinary sequence, we can reduce our array element size to 4-bits. We'll define the array as 8-bit values, but half the size as we need in bytes (plus 1 in case it is an odd-sized array), and store the elements two per byte in the array. The adds and compares can be done as SIMD (single instruction, multiple data) operations, incrementing or checking up to 16 elements per loop iteration by using some clever bit operations.

That's the concept. Now down to the code.

First, we need to allocate and initialize our array.

unsigned char *arr = malloc(n/2 + 1);

// Init all element values to 4:
memset(&arr, 0x44, n/2 + 1);

We're going to increment 16 elements at a time by casting a block of 8 array bytes to a uint_64, add 0x1111111111111111 and then skip to the next block. Repeat with 32-bit, 16-bit, 8-bit and 4-bit math for up to 8, 4, 2 or 1 remaining at the end of the operation.

Before each increment, anything that has a value of 4 needs to be decremented by 3 before the increment to keep the numbers in the proper position.

Here's the code (untested) for the increment command:

/**
   @param p
      p is the address of the byte with the first aligned element to be incremented, &arr[A/2] when A is even, &arr[A/2]+1 when A is odd.
   @param j
      j is the number of elements to increment.  (B-A) when A is even, (B-A-1) when A is odd.
 */ 
void increment_aligned_block(unsigned char *p, int j)
    uint64_t fours;

    while (j>16) {
       // Find the ones that are value 4
       fours = *p & 0x4444444444444444;
       // Decrement each that matches by 3
       *p -= (fours >> 1 | fours >> 2);

       // Add 1 to each of the 16 array elements in the block.
       (uint64_t)(*p) += 0x1111111111111111;
       p += 8; j -= 16;
    }
    if (j >= 8) {
        // repeat the above for 32-bits (8 elements)
        // left as an exercise for the reader.
        p += 4; j -= 8;
   }
    if (j >= 4) {
        // repeat the above for 16-bits (4 elements)
        // left as an exercise for the reader.
        p += 2; j -= 4;
    }
    if (j >= 2) {
        // repeat the above for 8-bits (2 elements)
        // left as an exercise for the reader.
        p += 1; j -= 2;
    }
    if (j == 1) {
        // repeat the above for 8-bits (1 elements)
        // left as an exercise for the reader.
    }
}

For the compare use:

/**
   @param p
      p is the address of the byte with the first aligned element to be counted, &arr[A/2] when A is even, &arr[A/2]+1 when A is odd.
   @param j
      j is the number of elements to count.  (B-A) when A is even, (B-A-1) when A is odd.
 */ 
int count_aligned_block(unsigned char *p, int j)
    int count = 0;
    uint64_t divisible_map;

    while (j > 16) {
        // Find the values of 4 in the block
        divisible_map = (uint64_t)(*p) & 0x4444444444444444;

        // Count the number of 4s in the block,
        // 8-bits at a time
        while (divisible_map) {
          switch (divisible_map & 0x44) {
            case 0x04:
            case 0x40:
                count++;
                break;
            case 0x44:
                count += 2;
                break;
            default:
                break;
          }
          divisible_map >>= 8;
        }
    }
    // Repeat as above with 32, 16, 8 and 4-bit math.
    // Left as an exercise to the reader

    return count;
}

You might have noticed the functions are called foo_aligned_block and p needs to be the byte of the first aligned element. What is that? Since we're packing two elements per byte, the starting element index must be aligned to an even number. If the command in the file is 0 0 30 , then we can call increment_algined_block(&arr[A/2], 30) , no problem. However, if the command in the file is 0 1 30 , then we need to have additional code to handle the unaligned first element at index 1, and then call increment_aligned_block(&arr[A/2 + 1], 29) . Again, left as an exercise to the reader.


I'd like to note this is not the most optimal it can be.

Unaligned accesses are usually pretty expensive. That is, reading an 8-byte value from an 8-byte aligned address is faster than from a non-aligned address. We can add additional optimizations to only call foo_aligned_block() such that all accesses are guaranteed to be aligned.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM