THU数据结构编程作业一:祖玛(Zuma)

来源:互联网 发布:逍遥模拟器优化cpu 编辑:程序博客网 时间:2024/06/06 18:40

* 祖玛(Zuma) *

描述
祖玛是一款曾经风靡全球的游戏,其玩法是:在一条轨道上初始排列着若干个彩色珠子,其中任意三个相邻的珠子不会完全同色。此后,你可以发射珠子到轨道上并加入原有序列中。一旦有三个或更多同色的珠子变成相邻,它们就会立即消失。这类消除现象可能会连锁式发生,其间你将暂时不能发射珠子。

开发商最近准备为玩家写一个游戏过程的回放工具。他们已经在游戏内完成了过程记录的功能,而回放功能的实现则委托你来完成。

游戏过程的记录中,首先是轨道上初始的珠子序列,然后是玩家接下来所做的一系列操作。你的任务是,在各次操作之后及时计算出新的珠子序列。

输入
第一行是一个由大写字母’A’~’Z’组成的字符串,表示轨道上初始的珠子序列,不同的字母表示不同的颜色。

第二行是一个数字n,表示整个回放过程共有n次操作。

接下来的n行依次对应于各次操作。每次操作由一个数字k和一个大写字母Σ描述,以空格分隔。其中,Σ为新珠子的颜色。若插入前共有m颗珠子,则k ∈ [0, m]表示新珠子嵌入之后(尚未发生消除之前)在轨道上的位序。

输出
输出共n行,依次给出各次操作(及可能随即发生的消除现象)之后轨道上的珠子序列。

如果轨道上已没有珠子,则以“-”表示。

样例
Input

ACCBA51 B0 A2 B4 C0 A

Output

ABCCBAAABCCBAAABBCCBA-A

限制
0 ≤ n ≤ 10^4

0 ≤ 初始珠子数量 ≤ 10^4

时间:2 sec

内存:256 MB

提示
列表


先上整个程序:

#include<iostream>#include<list>#include<cstdio>#define MAXSIZE 20000using namespace std;const int SZ = 1<<20;  //fast io  struct fastio{      char inbuf[SZ];      char outbuf[SZ];      fastio(){          setvbuf(stdin,inbuf,_IOFBF,SZ);          setvbuf(stdout,outbuf,_IOFBF,SZ);      }  }io;  int main(){    #ifndef _OJ_        freopen("input.txt", "r", stdin);        freopen("output.txt", "w", stdout);    #endif    char s[MAXSIZE];        char c;    int sz(0);//size of string    scanf("%c", &c);    while(c != '\n'){        s[sz++] = c;        scanf("%c",&c);         }    s[sz++] = '\0';    int Times;    scanf("%d", &Times);    int (*Insert)[2] = new int[Times][2]();    for(int i = 0; i < Times; ++i)        scanf("%d %c", &Insert[i][0], &Insert[i][1]);    for(int i = 0, j = 0; i < Times; ++i){        for( j = sz - 1; j >= Insert[i][0]; --j)            s[j+1] = s[j];        s[j+1] = Insert[i][1];        ++sz;        /*******************************/        int Location = Insert[i][0];//insert site        int p1= Location, p2 = Location, fix = Location;        int P1 = Location, P2 = Location;        int rep(1);//times of repetition        bool p1Stop = false, p2Stop = false;        while(!p1Stop || !p2Stop){            //move p1            while(!p1Stop){                if(p1 > 0 && s[--p1] == s[fix])                    ++rep;                else                    p1Stop = true;            }            //move p2            while(!p2Stop){                if(p2 < sz - 1 && s[++p2] == s[fix])                    ++rep;                else                     p2Stop = true;             }            if(rep >= 3){                if(s[p1] == s[fix])                    P1 = p1;                else                    P1 = p1 + 1;                if(s[p2] == s[fix] )                    P2 = p2;                else                    P2 = p2 -1;            }            //reset rep            if(rep >=3 && s[p1] == s[p2]){                              rep = 2;                fix = p1;                p1Stop = false;                p2Stop = false;            }        }        if(P2 - P1 >= 2){            for(int i = P1, j= P2;  j < sz - 1 ; ++i, ++j)                s[i] = s[j + 1];            sz -= (P2-P1+1);        }        /*******************************/   /*        bool Flag = true;        if(sz >= 4){            while(Flag){                Flag = false;                for(int i = 0; i < sz - 1; ++i){                    int tt = 1;//counter to indicate the times of repetition                    for(int j = i + 1; i < sz; ++j){                        if(s[j] == s[i])                            ++tt;                        else                            break;                    }                    if(tt >= 3){                        Flag = true;                        for(int k = i; k < sz-tt; ++k)                            s[k] = s[k + tt];                        sz -= tt;                    }                                   }            }        }*/        if(sz <= 1)            printf("-\n");        else            printf("%s\n", s);    }    delete[] Insert;    return 0;}

以下对程序的部分内容进行解释:
1、 OJ上测试的数据量是非常大的,所以在设计程序时必须考虑时间复杂度。首先为了实现快速输入输出,调大流缓冲区。

const int SZ = 1<<20;   struct fastio{   //fast io    char inbuf[SZ];      char outbuf[SZ];      fastio(){          setvbuf(stdin,inbuf,_IOFBF,SZ);          setvbuf(stdout,outbuf,_IOFBF,SZ);      }  }io; 

开始在OJ上测试时候总是只能通过95%,对于最后一组数据总是超时,而且在程序算法改进了以后(稍后会进行说明),依然超时,改进后的算法的时间复杂度应该是极大地降低了的。因为以前在自己的 linux 虚拟机上测试过数据量较大的数据总是出错,我就考虑到是否是和输入输出缓存有关系,毕竟自己只是刚入门的菜鸟,对缓存并没有足够的了解。在网上搜了很多,都提到了扩大缓冲区但是基本都没有解决方法。直到看到这位大神的代码,才恍然大悟:http://blog.csdn.net/baidu_23318869/article/details/41284075 。后来测试不仅最后一组数据通过而且速度也很快。我将这段代码加到前一个“范围查询”的程序中,速度果然加快了很多,膜拜大神~~~

2、虽然题目提示用列表解决,因为链表的插入和删除一个节点的时间复杂度为 O(1) ,但是我并没有使用列表,下次我想用列表重新实现一次。因为担心THU的OJ中没有c++的string头文件(上一篇的结尾已经解释),我用的是字符数组存储字符序列。注意为了方便输出我在数组的结尾插入了‘\0’,这样就可以以C字符串输出。关于需要插入的字符和插入的位置,我用了一个二维数组来存储,如下

int (*Insert)[2] = new int[Times][2]();    for(int i = 0; i < Times; ++i)        scanf("%d %c", &Insert[i][0], &Insert[i][1]);

虽然整个数组为整形,但是我通过 scanf 将数组的第二列调整输入为 char 型,这样就可以保存想要保存的数据,并能够方便获取。

3、程序的主题部分是个大的 for 循环。对于每一个有待插入的字符,首先进行插入 :

 for( j = sz - 1; j >= Insert[i][0]; --j)            s[j+1] = s[j];        s[j+1] = Insert[i][1];        ++sz;

后面就是对于连续出现的字符序列进行删除了。
起初,我使用的呗注释掉的那部分方法。通过对插入后的序列进行逐论扫描,每次扫描都删除一个连续字符序列,这种方法很笨但是确实很好实现。

bool Flag = true;        if(sz >= 4){            while(Flag){                Flag = false;                for(int i = 0; i < sz - 1; ++i){                    int tt = 1;//counter to indicate the times of repetition                    for(int j = i + 1; i < sz; ++j){                        if(s[j] == s[i])                            ++tt;                        else                            break;                    }                    if(tt >= 3){                        Flag = true;                        for(int k = i; k < sz-tt; ++k)                            s[k] = s[k + tt];                        sz -= tt;                    }                                   }            }        }

在OJ上测试后最后一组数据超时无法通过,我以为是因为自己的程序方法太笨复杂度较高引起的超时,其实主要的原因应该是前面提到的流缓存不够大的原因。但是以上错误的认为,让我不得不重新考虑算法的实现。

4、重新思考算法的实现。上面的方法在字符删除上耗时应该是最多了,每次都要对字符序列进行扫描,而且每次扫描只能删除一组重复序列,效率是很低的。于是,我希望找到一种方法提前知道哪些元素会被删除,最后一次性删除所有数据,这样就只需要一次删除操作。
想到了用指针的方法(我这里用的是数组的下标)进行标记。首先设置两个标志都指向插入点的字符,然后将两个标志分别向左和向右移动,记录字符,当左右指针对于同一个字符记录的次数大于等于3 的时候就说明两个指针之间的字符串是需要删除的。
整个过程如下图:
指针移动过程

红色的 C 是插入的字符,Rep用来记录重复次数。
···首先指针p1和p2都指向插入位置,然后分别向左和向右移动直到遇到不同的字符就停止;
···如果当两个指针都停止时候的Rep的值大于等于3,则确定p1和p2之间的字符是需要删除的。
···此时,如果p1 和 p2 所指向的字符相同,则说明有可能后续的字符还需要删除,于是将Rep置为2,并再次移动p1和p2,同时记录Rep,如此反复。
···直到p1 和p2指向不同的字符或者已经移动到字符串两端。

整个过程也并不复杂,首先的难点是如果p1 和 p2 向两边移动停止时Rep小于3时,如何确定之前记录的需要删除的字符串的位置,我这里增加了两个变量P1 和P2来记录前一次确定需要删除的字符串的位置。其次,当其中一个指针移动到字符串的一端时,端点的字符串可能需要删除也可能不需要删除,这是比较难确定的。这里我增加了一个判断来解决这个问题:

if(rep >= 3){        if(s[p1] == s[fix])             P1 = p1;        else             P1 = p1 + 1;        if(s[p2] == s[fix] )              P2 = p2;        else              P2 = p2 -1;            }

被这两个问题折磨的不要不要的~~~~ ヾ(≧O≦)〃嗷~
最后测试,发现运行明显快了不少。

另:写到这里,发现其实开始的插入操作也可以没有的,直接记录要插入的位置和字符,然后只需要做删除操作不就好了!发现自己智商又捉急了~ヾ(≧O≦)〃嗷~ヾ(≧O≦)〃嗷~ヾ(≧O≦)〃嗷~

1 0
原创粉丝点击