【问题标题】:How would you go about tackling this exercise?你将如何处理这个练习?
【发布时间】:2010-04-08 03:27:06
【问题描述】:

简介:

编辑:请参阅此问题底部的解决方案(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

【问题讨论】:

  • 不,很遗憾,除了按照所述执行删除操作之外,您无法移动任何东西。
  • 对列表进行排序将使答案始终为 1 或 2,我认为这不是预期的效果。
  • 肩膀向下,膝盖着地。
  • 它的相邻对的大小使这变得有趣。如果它被固定为 2 或更多,那么@Paradigm 的答案是最好的,但是这些组意味着您需要按正确的顺序将足够的字母放在一起才能删除它们。

标签: algorithm language-agnostic


【解决方案1】:

执行以下步骤:

  • 寻找一种模式,例如 H+G+H+。删除一个 G+,它会留下一个合并的 H+,其中一个或多个原始 H+H+ 由于长度限制而无法删除。否则,删除最长的合并 H+。
  • 同样适用于 G+H+G+。

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

最终你会得到一个 H+ 和一个 G+(假设你一开始就有 H 和 G)。删除那些。

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

【讨论】:

  • 除非我弄错了:这只是一种摆脱所有 h 和 g 的方法。但是我需要的是一种方法以尽可能少的步骤删除它们,然后输出需要多少步骤。
  • 已更新以删除 G+,它给出了最长的合并 H+。我想这会为你做的。
  • 嗯。看起来很有希望 - 虽然我想知道是否有这种情况不起作用。我会尝试一下并回帖。
  • 我认为他们可能会试图欺骗您留下一些太短而无法移除的 H+ 或 G+,因此确保算法专注于不留下 H+ 或 G+ 散乱者可能很重要。我已经更新了第一步以反映这一点。
  • 很高兴为您提供帮助,您的解决方案很好。祝你在比赛中好运!
【解决方案2】:

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

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

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

  • 为 h 保留一个计数器,为 g 保留一个计数器。
  • 从第一个字符开始,并将相应的计数器加 1。
  • 转到下一个字符,如果与上一个字符相同,则继续下一个字符。
  • 如果不同,则增加新字符的计数器。
  • 继续相同的过程,每次字符与前一个字符发生变化时,都会增加相应的计数器。

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

有没有更快的方法? (这真的有效吗?;)

【讨论】:

  • 这可能行不通。他们说 Hs 和 Gs 的最大数量是 100 - 所以我期待的东西肯定比 O(n) 效率低。我可以看到你想在这里做什么。但在更复杂的情况下,这不起作用。不过谢谢!
  • 实际上,尽管您将它们视为“集合”的想法非常聪明。
  • +1 - 请参阅我更新的问题以获取最终解决方案。您的分组想法(将它们分组)非常有帮助!
【解决方案3】:

这里有个想法 - 不失一般性,您可以用代表组中字母数的数字替换 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 -> 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 -> 无

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

在我看来,最佳步数始终是 = trunc((N+1)/2) - if 。所以这项工作似乎更像是寻找是否有可能减少 H-G 链 - 如果是,那么我们知道最小步骤数。

想法?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-13
    • 2019-01-27
    • 1970-01-01
    • 2020-01-27
    • 2021-03-10
    • 1970-01-01
    相关资源
    最近更新 更多