Joseph Problem(详细解法)

前言



  这几天学习了队列,于是尝试了一下Joseph Problem,然后一直有一个地方没有通过,终于经点拨后,通过了。对于队列的使用还是加深了印象。建议如果队列的练手可以去尝试解决这个问题。

正文



 

Joseph Problem(35分)

铺垫:

假设有N个人围成一圈,然后第一个人把第二个人杀了,刀子给第三个人,第三个人把第四个人杀了,以此类推......

首先注意:难点1:围成一圈,我们会想用什么数据结构呢?这么多人,首先应该想到用数组对不对,可是怎么表示围成一圈呢?重点来了,其实我们可以不断的把前面的挪到后面不就相当于围成了圈吗?对不对?

难点二:前一个人把后一个杀了?普通的应该会想把这个被杀的人的元素去掉,然后前一个元素往后面移,可是这样就达不到围成圆圈的效果了,重点来了,就是把这个杀人的人一开始也杀了,对!没看错,是把它也杀了,然后再把它放到放到数组的最某端就可以了。

好,难点讲诉完毕。

题目内容:

實作Joseph problem.

假設一開始有N個人,編號1~N,

按照順序以順時針圍成一個圓圈。

遊戲開始時,編號1的人拿刀。

之後每一輪刀子會被往下傳M個人,

而當輪最後拿到刀子的人會將他的下一個人殺掉,

殺完後刀子會再傳給被殺的下一個人。

這樣一輪就算結束。

遊戲會進行許多輪,直到只剩下最後一個人。

範例1:N=5, M=2

第一輪:刀子傳給3號,4號被殺,刀子再傳給5號 (1 2 3 5)

第二輪:刀子傳給2號,3號被殺,刀子再傳給5號 (1 2 5)

第三輪:刀子傳給2號,5號被殺,刀子再傳給1號 (1 2)

第四輪:刀子傳給1號,2號被殺,最後1號存活。

範例2:N=4, M=3

第一輪:刀子傳給4號,1號被殺,刀子再傳給2號 (2 3 4)

第二輪:刀子傳給2號,3號被殺,刀子再傳給4號 (2 4)

第三輪:刀子傳給2號,4號被殺,最後2號存活。

输入格式:

輸入第一行為一個數字T,代表測資的筆數。

接下來會有T筆測資,每一筆測資一行,

會有兩個數字N,M,數字間以空格區隔。

數字範圍:

T < 1000

0 < N <= 1000

0 < M <= 1000

输出格式:

輸出一行數字,將每筆測資最後存活下來的人的編號加總。

输入样例:

3

5 2

4 3

8 4

输出样例:

4

时间限制:1000ms内存限制:32000kb

大家先尝试解一下。再看文章比较好!

先理解题意:看第一个例子:刀子传两个人后到了第三个人,把第四个人杀了。加上又是圆圈,所以就相当与把两个人杀了挪到后面,然后就是成了前面的问题了对不对?好,开始写代码。

首先我们考虑杀人这个动作。

1 int Dequeue() {
2     int z;//记录哪个人被杀,不然要是没有记录,怎么去把它挪到后面
3     z = ch[Head];
4     Head = (Head + 1) % 1000;//就是把Head指向后一人就可以了,相当于这一圆圈的人没有它了
5     Number_of_Items--;//总人数减一
6     return z;
7 }

再考虑插到后面去这个动作:

 1 void Enqueue(int x) {
 2     if(Number_of_Items == 0) {//如果数组是空的,重新定义队列的头和尾
 3         Tail = Head = 0;
 4         ch[0] = x;//然后把它加进去
 5     } else {
 6         Tail = (Tail + 1) % 1000;//队尾指向后面一个,用于存储新的人
 7         ch[Tail] = x;
 8     }
 9     Number_of_Items++;//总人数加一10 }

接下来就是总体的设计了

        scanf("%d %d",&a,&b);//输入总人数和要传的人数     for(int i=1; i <= a; i++)
            Enqueue(i);//把这几人放到数组里面去
        for(int i=1; i <= a-1; i++) {//每一轮杀一个人,总共要杀n-1个人
            for(int k=0; k < b%Number_of_Items; k++) {
                j = Dequeue();
                Enqueue(j);
            }//每次把b个人挪到后面
            j = Dequeue();
            Dequeue();
            Enqueue(j);//就像铺垫所说的,杀两个人,再把第一个人放到数组后面去。
        }

注意:

1.增加数组元素时,注意要考虑当数组为空。

2.

for(int k=0; k < b%Number_of_Items; k++) {
                j = Dequeue();
                Enqueue(j);
            }

注意这段代码,就是这个地方卡了我很久,看到没,当b非常大的时候,运行时间要的非常久,这里已经有3个for循环了,所以想法设法降低算法复杂度。于是发现可以对b进行优化,这是关键点。

3.每一次进行加入数组元素时,总人数记得加一,删除数组元素时,总人数记得减一。

好,接下来写出总代码:

#include<stdio.h>
int ch[1000];
int Head,Tail,Number_of_Items;

void Enqueue(int x) {
    if(Number_of_Items == 0) {
        Tail = Head = 0;
        ch[0] = x;
    } else {
        Tail = (Tail + 1) % 1000;
        ch[Tail] = x;
    }
    Number_of_Items++;
}

int Dequeue() {
    int z;
    z = ch[Head];
    Head = (Head + 1) % 1000;
    Number_of_Items--;
    return z;
}

int main() {
    int n,j,a,b;
    int answer;
    int sum = 0;
    scanf("%d",&n);
    for(int i=0; i < n; i++) {
        scanf("%d %d",&a,&b);
        ch[1000] = 0;//注意每一次对数组的更新
        Number_of_Items=0;//对总人数的更新
        Tail = Head = 0;
        for(int i=1; i <= a; i++)
            Enqueue(i);
        for(int i=1; i <= a-1; i++) {
            for(int k=0; k < b%Number_of_Items; k++) {
                j = Dequeue();
                Enqueue(j);
            }
            j = Dequeue();
            Dequeue();
            Enqueue(j);
        }
        sum += ch[Head];
    }
    printf("%d",sum);
    return 0;
}

注意:每次数组用完之后,都得进行更新赋值为0,并且对总人数也得更新。

总结



  这题卡到的地方主要是如何降低算法复杂度,对其中一些变量进行优化。我也是经别人点拨才明白了这里的优化,还得多多学习。

2016-03-06 14:14:02

时间: 2024-08-28 20:53:06

Joseph Problem(详细解法)的相关文章

yzoi2226最小步数的详细解法

Description - 问题描述 在各种棋中,棋子的走法总是一定的,如中国象棋中马走“日”.有一位小学生就想如果马能有两种走法将增加其趣味性,因此,他规定马既能按“日”走,也能如象一样走“田”字.他的同桌平时喜欢下围棋,知道这件事后觉得很有趣,就想试一试,在一个(100*100)的围棋盘上任选两点A.B,A点放上黑子,B点放上白子,代表两匹马.棋子可以按“日”字走,也可以按“田”字走,俩人一个走黑马,一个走白马.谁用最少的步数走到左上角坐标为(1,1)的点时,谁获胜.现在他请你帮忙,给你A.

A+B Problem 详细解答 (转载)

此为详细装13版 转载自:https://vijos.org/discuss/56ff2e7617f3ca063af6a0a3 全文如下,未作修改,仅供围观,不代表个人观点: 你们怎么都在做网络流,不就是一道简单的递推吗   发表于2016-04-02 10:29 而且你们假惺惺的用网络流,过程中还是要用加法,我一个加法都没用. #include <cstdio> int m, n, a[32768][32768]; int main() { scanf("%d%d", &

hdu 1016 Prime Ring Problem DFS解法 纪念我在杭电的第一百题

Prime Ring Problem Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 29577    Accepted Submission(s): 13188 Problem Description A ring is compose of n circles as shown in diagram. Put natural num

yzoi1777倒水问题的详细解法

Description - 问题描述 x.y.z三个容器,其最大容量分别是xMAX升.yMAX升.zMAX升,这里规定100>xMAX>yMAX>zMAX.一开始x是装满了水的,现在要用这三个没有刻度的容器量出n升水来,请打印出最少的量取步骤. Input - 输入数据 输入只有一行,即4个整数: xMax  yMax  zMax n 其中100>xMax>yMax>zMax,n<100 Output - 输出数据 对于每个测试数据,输出其倒水步骤,如果步骤只有一

yzoi2223集合构造的详细解法

Description - 问题描述 集合M的定义如下: 1是M中的元素 如果x是M中的元素,那么2x+1和4x+5都是M中的元素 那么,集合M中,最小的n个数是哪些? Input - 输入数据 一个整数n(1<=n<=100 000) Output - 输出数据 n个从小到大的整数,空格分隔. 仔细一分析便可推知最后需要输出的数一定是单调递增的.在当时做这题的时候,旁边的gxy同学直接从1开始暴力枚举所有的奇数(2x+1和4x+5肯定是一个奇数),然后判断和前面的数是否构成2x+1或4x+5

有两杯,一个11L一个7L,水任用,得到2L 的详细解法

问题:有两个水杯,一个是11L一个是7L,水可以随便用,怎么得到2L 1.了解问题的本质 问题中给出了两个杯子,只有这两个杯子有量度,所以只能让杯中的水满进满出才能确定杯子中最后有多少水. 现在问题要求通过两个杯子中水的倒进倒出,最后正好多出2L. 我们不难想到,想要得到这2L,有两种方式: 1>给11L装满,然后倒进7L,然后剩下的倒进7L,再给11L倒满……(简单说:从11L满进,从7L满出) 2>给7L装满,然后倒进11L,然后剩下的倒进11L,再给7L倒满……(简单说:从7L满进,从1

最近公共祖先LCA详细解法

最近公共祖先(LCA)是树上倍增的一种典型例题. 问题描述:给定一棵具有n个节点的树,询问两个节点x,y的最近的公共祖先. 分析:首先我们想到的是暴力算法,当然这个算法妥妥的会TLE掉,所以我们采用倍增优化的方法来进行实现,也就是我们今天介绍的重点. 所谓倍增,就是按2的倍数来增大,按1,2,4,8,16......来跳.不过我们不能按正着的顺序跳,需要按......16,8,4,2,1的顺序来跳.因为如果从小的开始跳,有可能出现"悔棋"的现象(5 不等于 1 + 2 +4,这样就必须

Joseph Problem With Passwords In Java

问题描述: 编号为1,2,......,n的n个人(每个人的信息有编号.姓名和密码三项)按照顺时针方向围坐一圈, 每个人有且只有一个密码(正整数,密码用随机方式赋值,范围1-15).一开始任选一个正整数作为报数 上限值,从第一个人开始顺时针方向自1开始报数,报到m时停止报数.报m 的人出列,将他的密码作为新 的m 值,从他在顺时针方向的下一个人开始重新报数,如此下去,直到所有人全部出队为止.设计一个程 序来求出队顺序. 分析: 为解决约瑟夫问题而设计的单向循环链表,应实现如下功能 :1  添加元

[转] 一些图论、网络流入门题总结、汇总

最短路问题此类问题类型不多,变形较少 POJ 2449 Remmarguts' Date(中等)http://acm.pku.edu.cn/JudgeOnline/problem?id=2449题意:经典问题:K短路解法:dijkstra+A*(rec),方法很多相关:http://acm.pku.edu.cn/JudgeOnline/showcontest?contest_id=1144该题亦放在搜索推荐题中 POJ 3013 - Big Christmas Tree(基础)http://ac