约瑟夫环问题
约瑟夫环问题(Josephus)

算法设计
Josephus jp=new Josephus(); int a[]=new int[n]; for(int i=0;i<n;i++){ a[i]=i+1; } jp.SortArray(a,n,m,k,g); } public void show(int[]b,int g){ for(int i=b.length-g;i<b.length;i++){ System.out.print(b[i]+" "); } }
• b[c]=a[i]; • a[i]=0; • c++; • if(c==n) break; • } • System.out.print(“最后出列的 3人: "); • this.show(b,g); • } • }
• 1.数据选择: 要求:n<2^15; 1<=k<=n; 2.数据和结果显示:
(3)当然其中还是会存在一些漏洞,需要进 一步的改进。在计算机中是容不得丝毫的 错误的,这也让我们学到了面对科学要持 有严谨的态度,否则必定得不到应该有的 结果。
总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 循环数m 最后出列的3人 48 6 15 47 21 46 105 73 4 87 32 21 300 80 12 70 296 198 总人数n 起始号码k 循环数m 68 34 25
输出格式:
T行最后min(n,3)个出列的编号。 结果:6 1 5
问题背景
• 这个问题是以弗拉维奥•约瑟夫斯命名的, 它是1世纪的一名犹太历史学家。他在自己 的日记中写道,他和他的40个战友被罗马 军队包围在洞中。他们讨论是自杀还是被 俘,最终决定自杀,并以抽签的方式决定 谁杀掉谁。约瑟夫斯和另外一个人是最后 两个留下的人。约瑟夫斯说服了那个人, 他们将向罗马军队投降,不再自杀。
实验报告 约瑟夫问题

pCur->next = pNew;
pCur = pNew;
printf("结点%d,密码%d\n",pCur->id, pCur->cipher);
}
}
printf("完成单向循环链表的创建!\n");
}
(3)运行"约瑟夫环"问题
static void StartJoseph(NodeType **, int)
exit(-1);
}
pNew->id = iId;
pNew->cipher = iCipher;
pNew->next = NULL;
return pNew;
}
(6)测试链表是否为空,空为TRUE,非空为FALSE
static unsigned EmptyList(const NodeType *pHead)
实验内容
利用循环链表实现约瑟夫环求解。
实验说明
1.问题描述
约瑟夫问题的:编号为1,2,....,N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数),一开始任选一个正整数作为报数上限值M,从第一个人开始按顺时针方向自1开始顺序报数,报到M时停止报数。报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
{
if(!pHead)
{
return TRUE;
}
return FALSE;
}
实验中遇到的问题及解决方法
实验结果如下:
实验总结(结果和心得体会)
约瑟夫环问题小结

约瑟夫环问题⼩结⼀问题描述约瑟夫环问题的基本描述如下:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为1的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,要求找到最后⼀个出列的⼈或者模拟这个过程。
⼆问题解法在解决这个问题之前,⾸先我们对⼈物进⾏虚拟编号,即相当于从0开始把⼈物重新进⾏编号,即⽤0,1,2,3,...n-1来表⽰⼈物的编号,最后返回的编号结果加上1,就是原问题的解(为什么这么做呢,下⽂有解释)。
⽽关于该问题的解通常有两种⽅法:1.利⽤循环链表或者数组来模拟整个过程。
具体来讲,整个过程很明显就可以看成是⼀个循环链表删除节点的问题。
当然,我们也可以⽤数组来代替循环链表来模拟整个计数以及出列的过程。
此处只给出利⽤数组来模拟这个过程的解法,最终结果为最后⼀个出列的⼈的编号:#include<iostream>#include<unordered_map>#include<queue>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>#include<sstream>#include<set>#include<map>using namespace std;int main(){int n,m;cin>>n>>m;vector<int>rs(n);for(int i = 0 ; i < n; i++)rs[i] = i + 1;//对⼈物重新进⾏编号,从0开始int cur_index = 0;//当前圆桌状态下的出列⼈的编号int out_cnt = 0;//⽤以表⽰出列的⼈数int cnt = n;//表⽰当前圆桌的总⼈数while(out_cnt < n - 1)//当out_cnt等于n-1时,循环结束,此时圆桌师⽣最后⼀个⼈,即我们要的结果{if(cur_index + m > cnt){if((cur_index + m) % cnt == 0)//这种情况需要单独考虑,否则cur_index就变成负值了cur_index = cnt - 1;elsecur_index = (cur_index + m) % cnt - 1;}elsecur_index = cur_index + m - 1;cnt--;out_cnt++;cout<<"当前出列的为:"<<*(rs.begin() + cur_index)<<endl;rs.erase(rs.begin() + cur_index);//从数组中删去需要出队的⼈员}cout<<"最后⼀个出列的⼈物为:"<<rs[0]<<endl;}该⽅法的时间复杂度为O(nm),空间复杂度为O(n),整个算法的基本流程还是⽐较清晰的,相当于每次循环更新cur_cnt、cnt和out_cnt这三个变量,当out_cnt == n-1时,此时出队的⼈数⼀共有n-1⼈,圆桌上只剩下⼀个⼈了,停⽌循环。
模板约瑟夫环(Joseph)问题.ppt

最新 文档
10
4.详细设计
main()函数
Joseph()函数
从循环链表中按初始密码 依次找出对应出列序列
输出每个人持有的密码c
所有密码c输出后,删除相应 的节点,并释放所占的存储
空间
图5 输出序列的实现
最新 文档
11
5.测试报告
//尾插入法创建链表
void CreateLinkList(LinkList *&L,int n)
最新 文档
3
2.问题描述
编号是1,2,……,n的n个人按照顺时针方向围 坐一圈,每个人只有一个密码(正整数)。一 开始任选一个正整数作为报数上限值m,从第一 个人开始顺时针方向自1开始顺序报数,报到m 时停止报数。报m的人出列,将他的密码作为 新的m值,从他在顺时针方向的下一个人开始 重新从1报数,如此下去,直到所有人全部出 列为止。设计一个程序来求出出列顺序。
int i = 1;
c = L;
printf("输出出对序列:");
while (n)
{
while (i != m)
{
s = c;
c = c->next;
i++;
}
printf("%-3d",c->data);
m = c->cipher;
s->next = c->next;
free(c);
c = s->next;
8 这就是第三步的位置, 这时他的密码作为新的 m值,即m=9,同时得 到的第二个密码为9;9 号出去向下走9,到这 儿;继续走就行了(这 儿剩余的就是:1,2,
3,5,6,7,8,9)
数据结构约瑟夫环问题

数据结构实验报告题目:约瑟夫环问题一.设计内容[问题描述]约瑟夫环问题的一种描述是:编号为1, 2, 3,…,n的n个人按顺时针方向围坐一圈,每人手持一个密码(正整数)。
一开始任选一个整数作为报数上限值,从第一人开始顺时针自 1 开始顺序报数,报到m 时停止报数。
报m 的人出列, 将它的密码作为新的m 值,从他在顺时针方向上的下一个人开始重新从 1 报数, 如此下去直到所有人全部出列为止。
试设计程序实现之。
[基本要求] 利用循环链表存储结构模拟此过程,按照出列的顺序打印各人的编号。
[ 实验提示] 程序运行后首先要求用户指定初始报数上限值。
然后读取各人的密码。
设n<=30 。
程序执行后,要求用户在计算机终端上显示“提示信息”后,用键盘输入“提示信息”中规定的命令,以“回车符”为结束标志。
相应的输入数据和运算结果显示在其后。
二、设计目的1. 达到熟练掌握C++ 语言的基本知识和技能;2. 能够利用所学的基本知识和技能,解决简单的面向对象程序设计问题。
3. 把课本上的知识应用到实际生活中,达到学以致用的目的。
三、系统分析与设计(确定程序功能模块)1、为实现上述程序的功能,应以有序链表表示集合。
基本操作:InitList(&L)操作结果:构造一个空的有序表L。
DestroyList(&L)初始条件:有序表L 已存在。
操作结果:销毁有序表L。
ListEmpty(L)初始条件:有序表L 已存在。
操作结果:若L为空表,则返回TRUE,否则返回FALSE。
ListLength(L)初始条件:有序表L 已存在。
操作结果:返回L 中数据元素个数。
GetElem(L,i)初始条件:有序表L已存在,并且K i< ListLength(L)。
操作结果:返回L 中第i 个数据元素。
LocatePos(L,e)初始条件:有序表L已存在,e和有序表中元素同类型的值。
操作结果:若L中存在和e相同的元素,则返回位置;否则返回0。
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫环问题的简单解法(数学公式法)关于约瑟夫环问题,无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。
我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。
因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。
求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。
现在我们把他们的编号做一下转换:k-2 – n-2k-1 – n-1解x’ —- 解为x注意x’就是最终的解变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k)%n如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢?当然是先求(n-3)的情况—- 这显然就是一个倒推问题!下面举例说明:假设现在是6个人(编号从0到5)报数,报到(2-1)的退出,即 m=2。
那么第一次编号为1的人退出圈子,从他之后的人开始算起,序列变为2,3,4,5,0,即问题变成了这5个人报数的问题,将序号做一下转换:现在假设x为0,1,2,3,4的解,x’设为那么原问题的解(这里注意,2,3,4,5,0的解就是0,1,2,3,4,5的解,因为1出去了,结果还是一个),根据观察发现,x与x’关系为x’=(x+m)%n,因此只要求出x,就可以求x’。
约瑟夫斯问题

约瑟夫斯问题约瑟夫问题维基百科,⾃由的百科全书跳到导航跳到搜索约瑟夫问题(有时也称为约瑟夫斯置换),是⼀个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题⼜称为约瑟夫环。
⼈们站在⼀个等待被处决的圈⼦⾥。
计数从圆圈中的指定点开始,并沿指定⽅向围绕圆圈进⾏。
在跳过指定数量的⼈之后,处刑下⼀个⼈。
对剩下的⼈重复该过程,从下⼀个⼈开始,朝同⼀⽅向跳过相同数量的⼈,直到只剩下⼀个⼈,并被释放。
问题即,给定⼈数、起点、⽅向和要跳过的数字,选择初始圆圈中的位置以避免被处决。
历史这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的⼀名犹太历史学家。
他在⾃⼰的⽇记中写道,他和他的40个战友被罗马军队包围在洞中。
他们讨论是⾃杀还是被俘,最终决定⾃杀,并以抽签的⽅式决定谁杀掉谁。
约瑟夫斯和另外⼀个⼈是最后两个留下的⼈。
约瑟夫斯说服了那个⼈,他们将向罗马军队投降,不再⾃杀。
约瑟夫斯把他的存活归因于运⽓或天意,他不知道是哪⼀个。
[1]解法⽐较简单的做法是⽤循环单链表模拟整个过程,时间复杂度是O(n*m)。
如果只是想求得最后剩下的⼈,则可以⽤数学推导的⽅式得出公式。
且先看看模拟过程的解法。
Python版本-- coding: utf-8 --class Node(object):def init(self, value):self.value = valueself.next = Nonedef create_linkList(n):head = Node(1)pre = headfor i in range(2, n+1):newNode = Node(i)pre.next= newNodepre = newNodepre.next = headreturn headn = 5 #总的个数m = 2 #数的数⽬if m == 1: #如果是1的话,特殊处理,直接输出print (n)else:head = create_linkList(n)pre = Nonecur = headwhile cur.next != cur: #终⽌条件是节点的下⼀个节点指向本⾝for i in range(m-1):pre = curcur = cur.nextprint (cur.value)pre.next = cur.nextcur.next = Nonecur = pre.nextprint (cur.value)using namespace std;typedef struct _LinkNode {int value;struct _LinkNode* next;} LinkNode, *LinkNodePtr;LinkNodePtr createCycle(int total) {int index = 1;LinkNodePtr head = NULL, curr = NULL, prev = NULL;head = (LinkNodePtr) malloc(sizeof(LinkNode));head->value = index;prev = head;while (--total > 0) {curr = (LinkNodePtr) malloc(sizeof(LinkNode));curr->value = ++index;prev->next = curr;prev = curr;}curr->next = head;return head;}void run(int total, int tag) {LinkNodePtr node = createCycle(total);LinkNodePtr prev = NULL;int start = 1;int index = start;while (node && node->next) {if (index == tag) {printf("%d\n", node->value);prev = node->next;node->next = NULL;node = prev;} else {prev->next = node->next;node->next = NULL;node = prev->next;}index = start;} else {prev = node;node = node->next;index++;}}}int main() {if (argc < 3) return -1;run(atoi(argv[1]), atoi(argv[2]));return 0;}数学推导解法我们将明确解出{\displaystyle k=2}k=2时的问题。
循环队列之约瑟夫环问题

循环队列之约瑟夫环问题约瑟夫问题 约瑟夫环(约瑟夫问题)是⼀个数学的应⽤问题:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为k的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,直到圆桌周围的⼈全部出列。
通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。
循环队列求解(链式)#include<stdio.h>#include<stdlib.h>//循环队列//typedef int ElemType;typedef struct QueueNode{int data;struct QueueNode *next;}QueueNode;typedef struct Queue{QueueNode *front;QueueNode *rear;}Queue;void InitQueue(Queue *q){q->front=q->rear=NULL;}void EnQueue(Queue *q , int value){QueueNode *temp=(QueueNode*)malloc(sizeof(QueueNode));temp->data=value;if(q->rear==NULL){temp->next=temp;q->rear=q->front=temp;}else{temp->next=q->rear->next;q->rear->next=temp;q->rear=temp;}}//enter a element from the tailvoid DeQueue(Queue *q, int *value){QueueNode *temp=(QueueNode*)malloc(sizeof(QueueNode)); if(q->rear==NULL){return;}// It's nullelse if(q->rear->next==q->rear){*value=q->front->data;free(q->rear);q->rear=q->front=NULL;}//It just has one nodeelse{*value=q->front->data;temp=q->front;q->front=temp->next;q->rear->next=q->front;}//more one nodefree(temp);}//delete a element from the headint main(){Queue *q=(Queue*)malloc(sizeof(Queue));int i,m,n,count,temp;printf("请输⼊⼈数n和循环要报的数m(两数之间留个空格)\n"); scanf("%d%d",&n,&m);for(i=1;i<=n;i++)EnQueue(q,i);printf("出圈序列:\n");while(q->front){ count=1;while(count<m){q->front=q->front->next;q->rear=q->rear->next;count++;}count=1;DeQueue(q,&temp);printf("%d ",temp);}putchar('\n');}简单解法#include <stdio.h>int josephus(int n, int m) {if(n == 1) {return0;}else {return (josephus(n-1, m) + m) % n;}}int main() {int n, m;while (scanf("%d", &n) == 1) {if (!n) {break;}scanf("%d", &m);int result = josephus(n, m);printf("%d\n", result+1);}return0;}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
约瑟夫环问题
问题描述:
有n个人围成一个环,然后给从某个人开始顺时针从1开始报数,每报到m时,将此人出环杀死(当然不杀死也可以啊),然后从下一个人继续从1报数,直到最后只剩下一个人,求这个唯一剩下的存活的人是谁?
分析:
首先,我们要标示这n个人,别小看这一步,其实蛮重要的。
第一种标示方法是从0开始,将n个人标示为0~n-1,第二种方法是从1开始标示,将这n个人标示为1~n。
当然会有人说了,那我从x(x>=2)开始,将此n个数标示为x~x+n-1,其实我们可以把这种情况都归到第二种从1开始标示的情况,为什么可以,我们稍后分析。
第一种情况从0开始编号:
编号为k的人拖出去杀死之后,下一个要拖出去受死的人的编号为:(k+m)%n (假设当前有n个人还活在环中)。
第二种情况从1开始编号:
编号为k的人拖出去杀死之后,下一个要拖出去受死的人的编号为:(k+m-1)%n+1,于是我们就可以回答上面的问题了,如果从x开始编号的话,下一个拖出去受死的人的编号就应该是:(k+m-x)%n+x了。
其实,上面的这两种情况是完全可以在合并的,编号只是一个识别,就像名字一样,叫什么都没关系,从某个人开始出环,不管他们怎么编号,n个人出环的先后顺序都是一样的,最后该哪个人活下来是确定的,不会因为编号而改变,所以不管从几开始编号,都可以归纳为从0开始编号,其他的编号就是一个从0编号情况的一个偏移而已,从x编号的情况就相当于从0开始编号的情况下每个人的编号都+x,大小先后顺序不变~
于是,下面的讨论都是从0开始编号的~
怎么解决这个问题呢?
最简单的方法是模拟,模拟这个出环过程,可以使用链表也可以使用数组,时间复杂度都是O(n*m).当然,这种解法时间复杂度太高,不可取~
我们有O(n)的算法~
假设从编号为0的人开始报数,当然从编号为k的人开始报数的情况也是也可以解决的,只要稍微转化就可以,至于怎么解决?我们讲完从编号为0的人开始报数的情况就明白啦~
我们从0编号开始报数,第一个出环的人m%n-1,剩下的n-1个人组成一个新的约瑟夫环,接下来从m%n开始报数,令k=m%n,新环表示为:
k, k+1, k+2, ……n-1, 0, 1, 2, …..., k-2
我们对此环重新编号,根据上面的分析,编号并不会影响实际结果。
k → 0
k+1 → 1
k+2 → 2
...
k-2 → n-2
对应关系为:x’ = (x+k)%n (其中,x’是左侧的,x是右侧重新编号的)
重新编号之后,就成了求n-1约瑟夫环问题了,好似差不过已经解决了,我们递归求解就ok了~
但是,虽然怎么编号该哪个人最后剩下了就哪个人最后剩下来,但编号是用来确定人的,如果每次这样重新编号,编号就混乱了,我们无法让计算机知道到底是哪个人最后活下来了。
所以我们要对编号进行统一~
统一的方法就是利用上面提到的对应关系:x’ = (x+k)%n (其中,x’是左侧的,x是右侧重新编号的)。
我们假设最后活下来的在重新编号后的n-1个人的约瑟夫环中的位置是x,则它在n个人的约瑟夫环中的位置就是x’啦,即:f[n] = (f[n-1]+m%n)%n,化简为:f[n]=(f[n-1]+m)%n
然后,递归的递推关系就有了:
f[1] = 0
f[n]=(f[n-1]+m)%n (n >= 2)
如果只是求得最后一个存活下来的人的编号的话,可以不存储中间结果,编程实现及其简单:int main(void)
{
int n, m, i, s=0;
scanf("%d%d", &n, &m);
for (i=2; i<=n; i++) s=(s+m)%i;
printf ("The winner is %d\n", s);
}
下面我们来回答上面留下来的问题,如果我们从k开始报数,我们只要多加一步转化就ok 了,即:
k → 0
k+1 → 1
k+2 → 2
...
k-2 → n-2
k-1→ n-1
所以,如果从0开始编号并且从0编号的人开始报数的n个人的约瑟夫环问题的答案为s 的话,则从0开始编号并且从k编号的人开始报数的n个人的约瑟夫环问题的答案就是:(s+k)%n
上面的算法的时间复杂度为O(n),基本约瑟夫环问题就解决了~
但是,有时候O(n)的算法也可能接受不了~
对于m=2的情况,我们还有时间复杂度更低的公式算法~
公式是怎样的呢?我们先来看一组数据,其中编号还是从0开始的,我们枚举一些n的值,看m=2时的结果r:
n: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
r: 0 0 2 0 2 4 6 0 2 4 6 8 10 12 14
我们总结规律:
把n表示成n=2^m+k,则r=2*k
编码实现比较绕,仔细看看就明白了:
int main(int argc, char *argv[])
{
int n;
scanf("%d", &n);
int t = 1;
while (t<=n) t<<=1;
t>>=1;
n ^= t;
n<<=1;
printf("%d\n", n);
}
有了上面的知识准备就可以轻松搞定下面两个题目了~
/JudgeOnline/problem?id=2244
/JudgeOnline/problem?id=1781
上面的都只是基本的约瑟夫环问题,还有一类变态的约瑟夫环,它的m值不是固定的,这样我们就无法像上面的那样利用公式做到O(n)的时间复杂度了,只能使用最后一招,没招之招:模拟啦~ 但我们也不想让时间复杂度到O(n*m),所以我们要对模拟方法进行改进~
问题背景:
有n个人的约瑟夫环,然后每个人都有一个mi值,充当了下一轮报数的m值。
假设从编号为k的人开始游戏,游戏是这样玩的:先让k出环杀死,然后取得他的mi值,作为下一轮报数m值,即从编号k+1开始报数,当报数值到达mi时,将此人出环杀死,然后继续取刚出环的人的mi值作为下一轮的m值,继续游戏,直到最后只有一个人剩下。
算法讨论:
模拟的方法肯定是可以搞定的~模拟的方法有n-1次找人杀人操作,每次找人操作需要累计访问m个元素,才能确定要找的人。
所以如果我们可以快速的找到当前位置开始的剩余在环中的第m个人出环杀掉的话,时间复杂度应该可以降下来~
我们将环中的人的编号记录在线段树中,并且记录线段树中每个区间还剩下的活着的人数cnt域,每次杀一个人,我们就将其从线段树中删除,并且更新更新包含此人的区间的cnt 值。
这样我们就可以在O(lgn)的时间复杂度内取得还剩下的人中的第x个人的编号。
然后剩下的事就是计算每次出环的是剩下在环中的第几个人,也就是x值了。
怎么确定呢?
假设环中还有n个活人,某轮杀的是剩下在环中n个中的第k个人(0,1,….k),然后我们取得了它的mi值(怎么取得的呢?我们可以在线段树中以O(lgn)的时间复杂度查找此第k个人的编号,然后就可以取得他的mi值了),此时环中还剩下n-1个人,接下来要出环杀死的是谁呢?假设是剩下在环中的第x个人(0,1,2…x),则x=(k+mi-1)%(n-1),然后取得线段树中的第x个元素的mi值作为下一轮的m值,并将其从线段树中删除~继续下一轮杀人游戏~直到还剩下一个人在环中~
我们发现每轮确定要杀的人的编号只需要O(lgn)的时间复杂度,所以总的时间复杂度为O(n*lgn)~
有此知识储备可以挑战:
/JudgeOnline/problem?id=2886。