views:

224

answers:

3

Intro:

EDIT: See solution at the bottom of this question (c++)

I have a programming contest coming up in a bit, and I've been prepping :)

I'm practicing using these questions:

http://cemc.math.uwaterloo.ca/contests/computing/2009/stage2/day1.pdf

I'm looking at problem B ("Dinner").

Any idea where to start? I can't really think of anything besides the naive approach (ie. trying all permutations) which would take too long to be a valid answer.

Btw, the language there says c++ and pascal I think, but i don't care what language you use - I mean really all I want is a hint as to the direction I should proceed in, and perhpas a short explanation to go along with it. It feels like I'm missing something obvious...

Of course extended speculation is more than welcome, but I just wanted to clarify that I'm not looking for a full solution here :)


Short version of the question:

You have a binary string N of length 1-100 (in the question they use H's and G's instead of one's and 0's). You must remove all of the digits from it, in the least number of steps possible. In each step you may remove any number of adjacent digits so long as they are the same. That is, in each step you can remove any number of adjacent G's, or any number of adjacent H's, but you can't remove H's and G's in one step.

Example:

HHHGHHGHH

Solution to the example:

1. HHGGHH (remove middle Hs)
2. HHHH (remove middle Gs)
3. Done (remove Hs)
   -->Would return '3' as the answer.

Note that there can also be a limit placed on how large adjacent groups have to be when you remove them. For example it might say '2', and then you can't remove single digits (you'd have to remove pairs or larger groups at a time).


Solution

I took Mark Harrison's main algorithm, and Paradigm's grouping idea and used them to create the solution below. You can try it out on the official test cases if you want.

//B.cpp
//include debug messages?
#define DEBUG false


#include <iostream>
#include <stdio.h>
#include <vector>

using namespace std;

#define FOR(i,n) for (int i=0;i<n;i++)
#define FROM(i,s,n) for (int i=s;i<n;i++)
#define H 'H'
#define G 'G'

class String{
public:
    int num;
    char type;
    String(){
        type=H;
        num=0;
    }
    String(char type){
        this->type=type;
        num=1;
    }
};

//n is the number of bits originally in the line
//k is the minimum number of people you can remove at a time
//moves is the counter used to determine how many moves we've made so far
int n, k, moves;
int main(){

    /*Input from File*/
    scanf("%d %d",&n,&k);
    char * buffer = new char[200];
    scanf("%s",buffer);

    /*Process input into a vector*/
    //the 'line' is a vector of 'String's (essentially contigious groups of identical 'bits')
    vector<String> line;
    line.push_back(String());
    FOR(i,n){

        //if the last String is of the correct type, simply increment its count
        if (line.back().type==buffer[i])
            line.back().num++;

        //if the last String is of the wrong type but has a 0 count, correct its type and set its count to 1
        else if (line.back().num==0){
            line.back().type=buffer[i];
            line.back().num=1;
        }

        //otherwise this is the beginning of a new group, so create the new group at the back with the correct type, and a count of 1
        else{
            line.push_back(String(buffer[i]));
        }

    }

    /*Geedily remove groups until there are at most two groups left*/
    moves=0;
    int I;//the position of the best group to remove
    int bestNum;//the size of the newly connected group the removal of group I will create
    while (line.size()>2){

        /*START DEBUG*/
        if (DEBUG){
            cout<<"\n"<<moves<<"\n----\n";
            FOR(i,line.size())
                printf("%d %c \n",line[i].num,line[i].type);
            cout<<"----\n";
        }
        /*END DEBUG*/

        I=1;
        bestNum=-1;
        FROM(i,1,line.size()-1){
            if (line[i-1].num+line[i+1].num>bestNum && line[i].num>=k){
                bestNum=line[i-1].num+line[i+1].num;
                I=i;
            }
        }
        //remove the chosen group, thus merging the two adjacent groups
        line[I-1].num+=line[I+1].num;
        line.erase(line.begin()+I);
            line.erase(line.begin()+I);

            //we just performed a move
        moves++;
    }

    /*START DEBUG*/
    if (DEBUG){
        cout<<"\n"<<moves<<"\n----\n";
        FOR(i,line.size())
            printf("%d %c \n",line[i].num,line[i].type);
        cout<<"----\n";
        cout<<"\n\nFinal Answer: ";
    }
    /*END DEBUG*/


    /*Attempt the removal of the last two groups, and output the final result*/
    if (line.size()==2 && line[0].num>=k && line[1].num>=k)
        cout<<moves+2;//success
    else if (line.size()==1 && line[0].num>=k)
        cout<<moves+1;//success
    else
        cout<<-1;//not everyone could dine.

    /*START DEBUG*/
    if (DEBUG){
        cout<<" moves.";
    }
    /*END DEBUG*/
}

Some of the official test cases you can try :

  • Test Case 3

    8 2
    GHHGHGGH
    

    Answer: 4

  • Test Case 6

    20 2
    GGHGGHHGGGHHGHHGHHGG
    

    Answer: 6

  • Test Case 14

    100 4
    HGHGGGHGGGHGHGGGHHGHGGGHHGHHHGHGGHGGHHHGGHHGHHGHGHHHHGHHGGGHGGGHGHGHHGGGHGHGHGGGHHGHHHGHGGGHGGGHGHHH
    

    Answer: -1

    Explanation: -1 is outputted when there's no correct answer.

  • Test Case 18

    100 5
    GHGGGGGHGGGGGGGHHHHGGGGGHGGHHHGGGGGHHHHGGHHHHHGGGGGGHHHHHHGGGHHHHHGHHGGHHHHHGGGHHGGHHGGGGGGHHHGGGGHH
    

    Answer: 16

  • Test Case 21

    95 2
    GGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGHGHGHGHGHGHGHGHGHGHGHGHGHGHGHG
    

    Answer: 32

+1  A: 

This problem gets a bit simpler if you treat consecutive h's or consecutive g's as 1 h or 1 g respectively (they are treated the same way, in that ghhhhhg and ghg would require the same number of removals).

This reduces the problem to a set of h's and g's alternating. At this point, removing all of the lesser count of the 2 would give you the # of operations needed (plus 1 for the "other" remaining letters).

Algorithm can be done in O(n):

  • Keep a counter for h's, and a counter for g's.
  • Start at the first character, and increment the appropriate counter by 1.
  • Go to the next character, and if it is the same as the previous one, continue to the next one.
  • If it is different, increment the counter for the new character.
  • Continue the same process, incrementing the appropriate counter each time the character changes from the previous one.

After iterating through the set, the answer should be the lesser of the 2 counters (# of removals needed to remove the smaller # of characters), plus 1 (for the "other" characters, which will be left).

Is there a faster way? (and does this actually work? ;)

Paradigm
This probably won't work. They say that the maximum number of Hs and Gs is 100 - so I'm expecting something less efficient than O(n) for sure. I can see what you're trying to do here. But there are more complicated cases where this wouldn't work. Thanks though!
Cam
Actually though your idea with treating them as 'sets' is pretty smart.
Cam
+1 - see my updated question for the final solution. Your grouping idea (grouping them into sets) was very helpful!
Cam
+2  A: 

Do these steps:

  • Look for a a pattern such as H+G+H+. Remove a G+ that leaves a coalesced H+ where one or more of the original H+H+ could not have been removed because of the length restriction. Otherwise remove the longest coalesced H+.
  • Likewise for G+H+G+.

Repeat the above steps. each step will coalesce a larger group of H+ or G+.

Eventually you will have one H+ and one G+ (assuming you had both H and G to begin with). Remove those.

(H+,G+ means x or more H or G, where x is the minimum number that can be removed at one time.)

Mark Harrison
Unless I'm mistaken: This is simply a way to get rid of all the h's and g's. However what I need is a method to remove them in _the least number of steps possible_, and then output how many steps it would take.
Cam
updated to remove the G+ which gives the longest coalesced H+. I think this will do it for you.
Mark Harrison
Hmm. Looks promising - although I wonder if there are cases where this won't work. I'll try it out and post back.
Cam
I think they may try to trick you into leaving some H+ or G+ that are too short to be removed, so it might be important to make sure the algorithm concentrates on not leaving H+ or G+ stragglers. I've updated step one to reflect this.
Mark Harrison
Thanks. By the way, how would you classify this problem? Although it'll be cool to have this one solved, I'd especially like to be able to recognize other similar problems, as well as perform extra research about problems like this.
Cam
Brilliant!! This worked perfectly - totally makes sense! See code in my updated question.
Cam
Happy to help, your solution is nice. Good luck at the competition!
Mark Harrison
A: 

well here is a thought - w/o loss of generality you can replace sequences of Gs and Hs with number repesenting the count of letters in the group.

let's take the case#2: GGHGGHHGGGHHGHHGHHGG - it becomes 2 1 2 2 3 2 1 2 1 2 2

removing a group of letters and merging the two neighbours is removing a number and replacing the two adjacent with their sum: 2 1 2 2 3 2 1 2 1 2 2 -> 2 1 2 4 1 2 1 2 2 -> 2 1 2 4 1 2 3 -> 2 1 3 2 3 -> 2 3 3 -> 5 -> nil

if you notice, each time when we remove a group from the middle (not leftmost or rightmost), the length decreases by 2, so the number of steps necessary seems to be around N/2 (where N is the length of the list of numbers we started from) - the twist being that if N was even number, at the very end you will have list of 2 elements that will be cleared in 2 steps (so +1 extra step).

So seems to me the optimal number of steps is always = trunc((N+1)/2) - if at all possible. So the job seems to be more of the kind of finding if it's possible to reduce H-G chain at all - if it is, then we know the minimula number of steps.

Thoughts?

Nas Banov