Function Range Trick

Правка en7, от usernameson, 2017-03-21 04:17:11

Disclaimer

I do not know if this technique has another name. I came up with it to solve specific types of problems but it probably has been done by many others before.

The situation

Assume we are given a sequence of numbers a1, a2, ..., an - 1, an and we have a function f that is well defined for all sequences of numbers with the property f(b1, ..., bm) = f(f(b1, ..., bm - 1), bm) for any sequence b and the range of f has size K, which is relatively small. Our goal is to rearrange the numbers a1, ..., an so that when the sequence is entered into f in this order the value of f is optimised (minimised or maximised).

The solution

If we try to brute force all permutations of the sequence there are n! options which is usually too slow. However what we can do is use dynamic programming and for each sequence ending in aj with m terms store all possible values of f. Since we know the range of f is K at each step we are storing at most K terms.

Cost analysis

To calculate all possible values of f for a sequence ending in aj with m + 1 terms given we have calculated all possible values for f with sequence with m terms we need at most (n - 1)K calculations. Doing this for each term to calculate all possible values for f with a sequence of m + 1 terms gives at most n(n - 1)K calculations. Doing it n times since we need sequences of length n gives at most n2(n - 1)K. Finally to find the optimised value requires at most nK more calculations. So at worst we have n2(n - 1)K + nK calculations.

Noticing the situation

The nature of the function is what determines whether this situation occurs. If the function is based on bitwise operations and the values of the sequence are small there is a good chance that this technique applies. Also if the function is based on arithmetic modulo N where N is small this technique might also apply.

Worked Example

I used this technique to solve the problem trysail from topcoder. https://community.topcoder.com/stat?c=problem_statement&pm=14307. The general idea of the question is you have a sequence of at most 50 numbers that are between 0 and 255 and you want to split them into three non-empty groups so that when you take the xor of each group and sum them the result is maximised.

My approach: First we note when we xor any group of elements the result will be an integer between 0 and 255 inclusive. Next xor clearly satisfies the property required for f given above. These are strong hints that we can use the function range trick. Next we need to figure out a way to deal with three groups. Since the numbers are small we can store the xor of each group in an int letting the first 8 bits store group 1, the next 8 bits store group 2 and the next 8 bits store group 3. We also note since we are only concerned with maximising the sum we can treat certain groupings as equivalent to reduce the number of cases to consider.

Now assume for the first i elements we have stored all the xors of all possible groupings and we want to add the i+1th element and get the xors of all possible groupings with first i+1 elements. Turns out for each possible grouping there are six ways to do this up to equivalence, we either add the new element to one of the existing groups or give the new element its own group and combine the three previous groups into two. This is where the function range trick comes into play. Even though it looks like we are generating a lot of new groups every time we add a new element since the range of xor is limited many of these new groupings result in the same value for xor. Implementation wise we can use a set for storage so repeated elements will only be stored once.

Code ~~~~~~

include

include

include

include

using namespace std;

class TrySail{

public:
int get(vector<int> v){
    set<int> prev; 

    //put the first three elements in a separate group
    prev.insert(v[0]+(v[1]<<8)+(v[2]<<16));

    //given all possible xors when i elements are grouped
    //store all possible xors when i+1 elements are grouped
    for(int i=3; i<v.size(); i++){
        set<int> next;

        for(int j:prev){
            next.insert(j^v[i]);
            next.insert(j^(v[i]<<8));
            next.insert(j^(v[i]<<16));

            int x=j%(1<<8);
            int y=(j>>8)%(1<<8);
            int z=j>>16;
            next.insert(v[i]+((x^y)<<8)+(z<<16));
            next.insert(v[i]+((x^z)<<8)+(y<<16));
            next.insert(v[i]+((y^z)<<8)+(x<<16));
        }
        prev=next;
    }
    int ans=0;

    //find the largest answer
    for(int i:prev){
        int x=i%(1<<8);
        int y=(i>>8)%(1<<8);
        int z=i>>16;


        int sum=x+y+z;
        ans=max(ans,sum);
    }

    return ans;
}

};

~~~~~

As a final note this code passes the system tests but it gets quite close to the time limit for certain inputs with 50 elements.

Теги dp, math

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en10 Английский usernameson 2017-03-21 04:21:24 0 Added a worked example (published)
en9 Английский usernameson 2017-03-21 04:19:46 2 Tiny change: '\n\nCode\n~~~~~\n#' -> '\n\nCode\n\n~~~~~\n#'
en8 Английский usernameson 2017-03-21 04:18:25 1 Tiny change: 'ode\n~~~~~~\n#in' -> 'ode\n~~~~~\n#in'
en7 Английский usernameson 2017-03-21 04:17:11 3310 Added a worked example (saved to drafts)
en6 Английский usernameson 2017-03-20 07:57:58 0 (published)
en5 Английский usernameson 2017-03-20 07:56:20 14
en4 Английский usernameson 2017-03-20 07:54:00 1 Tiny change: 'f(f(b_{1},ldots,b_{m' -> 'f(f(b_{1},\ldots,b_{m'
en3 Английский usernameson 2017-03-20 07:53:32 34
en2 Английский usernameson 2017-03-20 07:52:22 32
en1 Английский usernameson 2017-03-20 07:34:54 1919 Initial revision (saved to drafts)