你会如何解决这个问题呢?

时间:2010-04-08 03:27:06

标签: algorithm language-agnostic

说明:

编辑:请参阅此问题底部的解决方案(c ++)

我有一个编程竞赛,我一直在准备:)

我正在练习使用这些问题:

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

我在看问题B(“晚餐”)。

知道从哪里开始?除了天真的方法(即尝试所有排列)之外,我无法想到任何需要太长时间才能成为有效答案的方法。

顺便说一句,那里的语言是c ++和pascal我认为,但是我不在乎你用的是什么语言 - 我的意思是我想要的只是暗示我应该继续进行的方向,并简要解释一下沿着这个走。感觉我错过了一些明显的东西......

当然,扩展的猜测非常受欢迎,但我只是想澄清一点,我不是在寻找一个完整的解决方案:)


问题的简短版本:

你有一个长度为1-100的二进制字符串N(在问题中他们使用H和G而不是一个和0)。您必须在可能的最少步骤中删除其中的所有数字。在每个步骤中,您可以删除任意数量的相邻数字,只要它们相同即可。也就是说,在每个步骤中,您可以删除任意数量的相邻G或任意数量的相邻H,但是您不能一步删除H和G.

示例:

HHHGHHGHH

示例解决方案:

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

请注意,删除它们时,相邻组的大小也可能存在限制。例如,它可能会说'2',然后您无法删除单个数字(您必须一次删除对或更大的组)。


解决方案

我使用Mark Harrison's main algorithmParadigm's grouping idea并使用它们来创建下面的解决方案。如果您愿意,可以在official test cases上试用。

//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*/
}

您可以尝试的一些official test cases

  • 测试案例3

    8 2
    GHHGHGGH
    

    答案:4

  • 测试案例6

    20 2
    GGHGGHHGGGHHGHHGHHGG
    

    答案:6

  • 测试案例14

    100 4
    HGHGGGHGGGHGHGGGHHGHGGGHHGHHHGHGGHGGHHHGGHHGHHGHGHHHHGHHGGGHGGGHGHGHHGGGHGHGHGGGHHGHHHGHGGGHGGGHGHHH
    

    答案:-1

    说明:当没有正确答案时输出-1。

  • 测试案例18

    100 5
    GHGGGGGHGGGGGGGHHHHGGGGGHGGHHHGGGGGHHHHGGHHHHHGGGGGGHHHHHHGGGHHHHHGHHGGHHHHHGGGHHGGHHGGGGGGHHHGGGGHH
    

    答案:16

  • 测试案例21

    95 2
    GGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGGHHGHGHGHGHGHGHGHGHGHGHGHGHGHGHGHG
    

    答案:32

3 个答案:

答案 0 :(得分:2)

执行以下步骤:

  • 寻找一个像H + G + H +这样的模式。除去由于长度限制而无法移除原始H + H +中的一个或多个的G +,其离开合并的H +。否则删除最长的合并H +。
  • 同样适用于G + H + G +。

重复上述步骤。每一步都会合并一大组H +或G +。

最终你会有一个H +和一个G +(假设你有H和G两个开头)。删除那些。

(H +,G +表示x或更多H或G,其中x是一次可以删除的最小数。)

答案 1 :(得分:1)

如果你将连续的h或连续的g分别处理为1小时或1克,这个问题会变得更简单一些(它们的处理方式相同,因为ghhhhhg和ghg需要相同数量的删除)。

这将问题简化为一组h和g的交替。此时,删除2的所有较少计数将为您提供所需的操作数(加上“其他”剩余字母的1)。

算法可以在O(n)中完成:

  • 保留一个h的计数器和一个g的计数器。
  • 从第一个字符开始,并将适当的计数器递增1.
  • 转到下一个字符,如果它与上一个字符相同,请继续下一个字符。
  • 如果不同,请递增新字符的计数器。
  • 继续相同的过程,每次角色从前一个角色改变时递增适当的计数器。

在遍历集合之后,答案应该是2个计数器中较小的一个(删除较小字符数所需的删除次数)加1(对于“其他”字符,将留下)。 / p>

有更快的方法吗? (这实际上有用吗?;)

答案 2 :(得分:0)

这里有一个想法 - 没有失去一般性你可以用数字代替Gs和Hs的序列来重复该组中的字母数。

让我们来看看案例#2:GGHGGHHGGGHHGHHGHHGG - 它变为2 1 2 2 3 2 1 2 1 2 2

删除一组字母并合并两个邻居正在移除一个数字并用它们的总和替换相邻的两个: 2 1 2 2 3 2 1 2 1 2 2 - &gt; 2 1 2 4 1 2 1 2 2 - &gt; 2 1 2 4 1 2 3 - &gt; 2 1 3 2 3 - &gt; 2 3 3 - &gt; 5 - &gt;零

如果你注意到,每当我们从中间(不是最左边或最右边)移除一个组时,长度减少2,所以所需的步数似乎在N / 2左右(其中N是长度我们开始的数字列表) - 扭曲是如果N是偶数,最后你将有2个元素的列表,将分两步清除(所以+1额外步骤)。

在我看来,最佳步数总是= trunc((N + 1)/ 2) - if 。因此,如果可以减少H-G链,那么这项工作似乎更像是一种发现 - 如果是,那么我们就知道最小步数。

思想?

相关问题