信息学奥赛一本通 2037:【例5.4】约瑟夫问题 | 1334:【例2-3】围圈报数 | 洛谷 P1996 约瑟夫问题

【题目链接】

ybt 2037:【例5.4】约瑟夫问题
ybt 1334:【例2-3】围圈报数
洛谷 P1996 约瑟夫问题

【题目考点】

1. 循环遍历数组

假设数组下标为1~n,循环控制变量i从1遍历到n后,再重新赋值为1,再遍历到n,重复此过程,直到满足某一条件,跳出循环。

int i = 1;
while(某种循环条件)
{
	//...
	i++; //或将这一段写为:i = i + 1 == n ? 1 : i + 1;
	if(i == n)
		i = 1; 
}

如果数组下标为0~n-1,可以写为:

for(int i = 0; 某种循环条件; i = (i + 1)%n)
{
	//...
}

2. 队列

3. 链表

考察:单链表、环形链表、stl list

【解题思路】

解法1:循环遍历数组

设布尔数组,表示某号人是否已经出列。循环遍历数组,如果遇到不在列的人,就跳过,从当前位置开始找到m-1个在列的人,再找下一个在列的人,就是要出列的人。将该人编号输出,出列。重复上述过程n次,每次让一个人出列。

解法2:使用队列

假设这n个人排成一队,在队头数人数,每数一个人,就让这个人出队,然后在队尾入队。数到第m个人时,输出这个人的编号,让该人出队,不再入队。重复上述过程n次。

解法3:环状链表

模拟,每个人作为一个结点,依次指向下一个结点,形成单链表,最后一个结点的下一个结点设为第一个结点,形成环状链表。模拟题中过程,设一个指针指向第一个结点,指针向后移动m-1次,指向第m结点,将此时指向的结点的值输出,而后删除该结点,并指向下一个结点,如此循环n次。

【题解代码】

解法1:循环遍历数组

#include 
using namespace std;
int main()
{
	bool isOut[1005] = {};//isOut[i]:第i人是否出列,初值为false,都没有出列。使用下标:0~n-1 
	int n, m, p = 0;//p:当前位置
	cin >> n >> m;
    for(int i = 1; i <= n; ++i)//一共输出n次
    {
    	for(int j = 0; j < m-1; ++j)//找m-1个在列的人
    	{
			while(isOut[p] == true)
				p = (p+1)%n;
			p = (p+1)%n;
		}//此时p指向第m-1个数的下一个位置 
		while(isOut[p] == true)//再找下一个人,就是第m个在列的人
			p = (p+1)%n;
		isOut[p] = true;//此时p指向第m个人 让该人出列 
		cout << p+1 << ' ';//下标从0开始,人编号从1开始,从下标转为人编号,需要加1 
	}
    return 0;
}

另第一种做法:

#include
using namespace std;
int main()
{
	int n, m, a[105], p;//p:当前关注的人的下标 
	bool isOut[105] = {};//isOut[i]:a[i]人是否在圈外 
	cin >> n >> m;
	for(int i = 1; i <= n; ++i)
		a[i-1] = i;
	p = n-1;//从n-1位置开始,每次向后找m次,找到的是第m个人 
	for(int j = 1; j <= n; ++j)
	{
		int i = 1;
		while(i <= m)
		{
			p = (p + 1) % n;
			if(isOut[a[p]])
				continue;
			else
				i++;
		}
		cout << a[p] << ' ';
		isOut[a[p]] = true;
	}
	return 0;
}

解法2:使用队列

#include 
using namespace std;
int main()
{
    queue<int> que;
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        que.push(i);
    for(int i = 1; i <= n; ++i)
    {
        for(int i = 1; i <= m - 1; ++i)
        {//将队头的人出队后,再入队到队尾
            que.push(que.front()); 
            que.pop();
        }
        cout << que.front() << ' ';//此时队头是要出列的人 
        que.pop();
    }
    return 0;
}

解法2:环状链表

  • 写法1:环状单链表
#include 
using namespace std;
struct Node
{
    int val;
    int next;
};
Node node[1005];//结点池 
int p;
int main()
{
    int n, m;
    cin >> n >> m;
    int first, tail, np;//first:指向第一个结点 tail:尾指针 np:新结点地址 
    np = ++p;//第一个结点的位置
    node[np].val = 1;
    first = tail = np;
    for(int i = 2; i <= n; ++i)
    {//尾插法构建单链表
        np = ++p;
        node[np].val = i;
        node[tail].next = np;
        tail = np;
    }
    node[tail].next = first;//最后一个结点的next指向第一个结点,形成环状链表 
    int sel = tail, del;//判断是否要删掉sel的下一个结点,最初sel的next就是第1结点, del:待删除的结点
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m-1; ++j)//最初sel的next是第1结点。向后移动m-1次后,sel的next就是第m个结点。 
            sel = node[sel].next;
        del = node[sel].next;//sel的下一个结点del就是应该删除的结点
        node[sel].next = node[del].next;//删除结点del,删除后,sel的下一个结点,就是下面要看的m个结点中的第一个 
        cout << node[del].val << ' ';//输出被删除结点的值,也就是出列的人的编号 
    }
	return 0;
}
  • 写法2:使用stl list,实际是双向链表
#include
using namespace std;
int main()
{
    list<int> li;
    int n, m, ct = 0;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        li.push_back(i);
    list<int>::iterator it = li.begin();
    while(li.empty() == false)
    {
        ct++;
        if(ct == m)
        {
            cout << *it << ' ';
            it = li.erase(it);
            ct = 0;
        }
        else
        	it++;
        if(it == li.end())
            it = li.begin();
    }
    return 0;
}

你可能感兴趣的:(信息学奥赛一本通题解,洛谷题解,c++,约瑟夫)