约瑟夫环问题

约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,接着下一个继续报数、杀人……最后剩下一个,其余人都已被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。

链表

由于STL中没有链表的模板,需要自己写一个模板。本着“不要重复造轮子”的精神,我找了一个模板直接用。。
以下是链表的实现,不用看,直接跳到第二段代码里

/*header.h*/
#pragma once
#include <iostream>
using namespace std;
template <class T>
struct LinkNode //节点类定义
{
    T data;                                                 //数据域
    LinkNode<T> *next;                                      //链指针域
    LinkNode(LinkNode<T> *ptr = NULL) { this->next = ptr; } //初始化指针域的构造函数
    LinkNode(const T &item, LinkNode<T> *ptr = NULL)        //初始化数据成员和指针成员和指针的构造函数
    {
        this->data = item;
        this->next = ptr;
    }
};

template <class T>
class List //用头结点的数据域表示链表元素数量
{
protected:
    LinkNode<T> *first;
public:
    List()
    {
        first = new LinkNode<T>;
        first->data = 0;
    } //无参数构造
    List(const T &x)
    {
        this->first = new LinkNode<T>;
        this->first->data = 0;
        //first->data = 1;
        this->inputHead(x);
    }                                                    //含有参数的构造函数
    List(List<T> &L);                                    //拷贝构造
    ~List() { makeEmpty(); }                             //析构函数
    void makeEmpty();                                    //将链表置空的函数
    int Length() const { return this->first->data; }     //计算链表长度的函数
    LinkNode<T> *getHead() const { return this->first; } //返回附加头结点地址
    LinkNode<T> *getRear() const;                        //返回尾部指针
    void inputHead(T head);                              //头插
    void inputRear(T rear);                              //尾插
    void output();                                       //将链表打印出来
    bool IsEmpty() const { return !this->first->data; }
    void Sort();                    //排序
    bool Insert(int i, T &x);       //在第i个位置插入x
    bool Remove(int i, T &x);       //删除第i个元素,将第i个元素的data赋值给x
    bool RemoveNode(LinkNode<T> *N); //删除N节点后面那个节点
    T *getData(int i);              //返回第i个元素的data地址
    void setData(int i, T &x);      //将第i个元素的data值更改为x
    LinkNode<T> *Search(T x);       //查找链表中第一个含有data为x的元素,返回x节点指针
    LinkNode<T> *Locate(int i);     //返回第i个元素的指针
    List<T> &operator=(List<T> &L); //符号重载,赋值
};
template <class T>
List<T> &List<T>::operator=(List<T> &L)
{
    if (!L.IsEmpty())
    {
        LinkNode<T> *srcptr = L.first, *desptr = this->first;
        while (srcptr->next != NULL)
        {
            desptr->data = srcptr->data;
            desptr->next = new LinkNode<T>;
            srcptr = srcptr->next;
            desptr = desptr->next;
        }
        desptr->data = srcptr->data;
    }
}
template <class T>
LinkNode<T> *List<T>::Locate(int i) //找第i个元素,找到返回地址,找不到返回头结点地址
{
    if (i > 0 && i < this->first->data)
    {
        int j = 0;
        LinkNode<T> *tmp = this->first;
        while (j != i)
        {
            tmp = tmp->next;
            ++j;
        }
        return tmp;
    }
    return this->first;
}
template <class T>
LinkNode<T> *List<T>::Search(T x)
{
    if (!this->IsEmpty())
    {
        LinkNode<T> *tmp = this->first->next;
        while (tmp->data != x && tmp->next != NULL)
        {
            tmp = tmp->next;
        }
        if (tmp->data == x)
            return tmp;
    }
    return this->first;
}
template <class T>
void List<T>::setData(int i, T &x)
{
    if (i > 0 && i <= this->first->data)
    {
        int j = 0;
        LinkNode<T> *tmp = this->first;
        while (j != i)
        {
            tmp = tmp->next;
            ++j;
        }
        tmp->data = x;
    }
}
template <class T>
T *List<T>::getData(int i)
{
    if (i > 0 && i <= this->first->data)
    {
        LinkNode<T> *tmp = this->first;
        int j = 0;
        while (j != i)
        {
            tmp = tmp->next;
            ++j;
        }
        return &tmp->data;
    }
}
template <class T>
bool List<T>::Remove(int i, T &x)
{
    if (i > 0 && i <= this->first->data)
    {
        LinkNode<T> *tmp = this->first, *p;
        if (i != 1)
        {
            int j = 0;
            while (j != i - 1)
            {
                tmp = tmp->next;
                ++j;
            }
            p = tmp->next;
            tmp->next = p->next;
            x = p->data;
            delete p;
        }
        else
        {
            p = tmp->next;
            x = p->data;
            tmp->next = p->next;
            delete p;
        }
        --this->first->data;
        return true;
    }
    return false;
}
template <class T>
bool List<T>::RemoveNode(LinkNode<T> *N)
{
    LinkNode<T> *tmp = N->next;
    N->next = tmp->next;
    if(tmp==this->first->next){
        this->first->next=tmp->next;
    }
    delete tmp;
    --this->first->data;
    return true;
}
template <class T>
bool List<T>::Insert(int i, T &x)
{
    if (i > 0 && i < this->first->data + 2)
    {
        if (i == this->first->data + 1)
        {
            this->inputRear(x);
            return true;
        }
        else if (i == 1)
        {
            this->inputHead(x);
            return true;
        }
        int j = i - 1;
        LinkNode<T> *tmp = new LinkNode<T>, *p = this->first;
        tmp->data = x;
        while (j)
        {
            p = p->next;
            --j;
        }
        tmp->next = p->next;
        p->next = tmp;
        ++this->first->data;
        return true;
    }
    else
        return false;
}
template <class T>
void List<T>::Sort() //排序有两类方法,一种是改变指针指向的,一种是仅为元素data排序,而不改变指针指向
{
    if (this->first->data > 1)
    {
        int i = this->first->data, j;
        LinkNode<T> *p = this->first, *q;
        while (i)
        {
            p = p->next;
            q = p->next;
            j = i - 1;
            while (j)
            {
                if (p->data > q->data)
                {
                    p->data = p->data + q->data;
                    q->data = p->data - q->data;
                    p->data = p->data - q->data;
                    q = q->next;
                }
                --j;
            }
            --i;
        }
    }
}
template <class T>
void List<T>::inputHead(T head)
{
    LinkNode<T> *tmp = new LinkNode<T>;
    if (tmp == NULL)
    {
        cerr << "内存分配错误!\n"
             << endl;
        exit(-1);
    }

    if (this->first->next != NULL)
    {
        tmp->next = this->first->next;
        this->first->next = tmp;
    }
    else
    {
        this->first->next = tmp;
        tmp->next = NULL;
    }
    tmp->data = head;
    ++this->first->data;
}
template <class T>
void List<T>::inputRear(T rear)
{
    LinkNode<T> *tmp = new LinkNode<T>;
    if (tmp == NULL)
    {
        cerr << "内存分配错误!\n"
             << endl;
        exit(-1);
    }

    LinkNode<T> *p = this->getRear();
    p->next = tmp;
    tmp->data = rear;
    ++this->first->data;
}
template <class T>
void List<T>::output()
{
    LinkNode<T> *p = this->first->next;
    while (p != NULL)
    {
        cout << p->data << "->";
        p = p->next;
    }
    cout << "over" << endl;
}
template <class T>
List<T>::List(List<T> &L)
{
    T value;
    LinkNode<T> *srcptr = L.getHead();
    LinkNode<T> *desptr = this->first = new LinkNode<T>;
    this->first->data = srcptr->data;
    while (srcptr->next != NULL)
    {
        value = srcptr->next->data;
        desptr->next = new LinkNode<T>(value);
        desptr = desptr->next;
        srcptr = srcptr->next;
    }
    desptr->next = NULL;
}
template <class T>
void List<T>::makeEmpty()
{
    LinkNode<T> *p, *q = this->first->next;
    this->first->data = 0;
    while (q != NULL)
    {
        p = q;
        q = q->next;
        delete p;
    }
}
template <class T>
LinkNode<T> *List<T>::getRear() const
{
    LinkNode<T> *p = this->first;
    while (p->next != NULL)
        p = p->next;
    return p;
}

用链表解此题的思路是:首先让尾节点指向第一个结点(注意头节点也指向第一个结点),这样就组成了环形链表。然后根据题意,开始遍历链表,遍历到第N个时,删除此结点,计数器归0,如此循环直至链表中只剩下一个结点。

#include <bits/stdc++.h>
#include "myList.h"
using namespace std;
int main()
{
    int N, M;
    cin >> N >> M;
    List<int> L(1);
    for (int i = 2; i <= N; i++)
    {
        L.inputRear(i);
    }
    auto p = L.getHead()->next;
    L.getRear()->next = p;
    int now = 1;
    while (1)
    {
        if (now == M-1 )
        {
            //cout<<p->next->data<<" ";
            L.RemoveNode(p);
            now = 0;
        }
        now++;
        p = p->next;
        if (L.getHead()->data == 1)
            break;
    }
    cout<<endl<<L.getHead()->next->data;
    system("pause");
    return 0;
}

输入100、10时,输出26,结果正确。可以看出思路简单清晰,但是链表真的太麻烦了,首先要写链表的定义,再把它们链接成环形链表,再一个个报数、删除结点。于是我在百度百科中找到了数组的解法。

数组

在链表中,死人的节点被删除了,剩下的环形链表继续。在数组中可以不删除死人节点,仅将其状态置为1,遍历时,遇到状态为1的点就跳过去,也相当于死人未参加游戏。但是实际上还是遍历到了,若有1亿人参与游戏,就算到最后一轮游戏仍需要遍历1亿遍,而链表此时只需要遍历2遍。
数组方法的优点是代码简洁。

#include<iostream>
using namespace std;
main()
{
    bool a[101]={0};
    int n,m,death=0,index=0,say=0;
    cin>>n>>m;
    while(death!=n)
    {
        ++index;//逐个枚举圈中的所有位置
        if(index>n)
            index=1;//数组模拟环状,最后一个与第一个相连
        if(!a[index])
            say++;//第t个位置上有人则报数
        if(say==m)//当前报的数是m
        {
            say=0;//计数器清零
            cout<<index<<' ';//输出被杀人编号
            a[index]=1;//此处人已死,设置为空
            death++;//死亡人数+1
        }
    }//直到所有人都被杀死为止
    system("pause");
}

DP

如果规定报3死,5人玩最后剩x并称x赢了,4人玩最后剩y,x和y有关系吗?我们来看一个具体的例子:
在这里插入图片描述

  1. 开始的时候,大家的序号是1234512345,如果变成123456789……这样的序列,那么让新序列号mod n即可得到正确序号,如上图我写的1-8
  2. 第一轮3死了,从4继续报数,报4个数所以在7这里停下。
  3. 第二轮4567号报数,谁会赢呢?想想1234号报数,若第二个人赢(2号赢),4567号报数也是第二个人赢(5号)。 那么就得到一个映射关系,若4人(1234号)报数x赢,则4567号报数x+3赢。但是这个x+3有可能大于5,例如7号赢,那么mod5得2也就是2号赢。

再来看抽象的例子:n人玩报k死。
在这里插入图片描述

  1. 第一轮k死了,从k+1往后报n-1个数,报到n+k-1正好n-1个数
  2. 那么从k+1号报数到n+k-1号中,谁会赢呢?
  3. 假设我们知道了从1号到n-1号报数中x号赢,那么这个问题的答案就是x+k号,因为他们如上图存在非常简单的映射关系(+k)。但是x+k号可能超过n了,(x+k)mod n才是我们的答案。

最后总结一下:n人玩报k死,胜者为dp[n],那么 d p [ n ] = ( d p [ n − 1 ] + k ) m o d   n dp[n] = ( dp[n-1] + k ) mod\ n dp[n]=(dp[n1]+k)mod n,而且 d p [ 1 ] = 0 dp[1] = 0 dp[1]=0 d p [ n ] dp[n] dp[n]为我们要的答案

这里有个坑,我设置初始状态dp[1] = 1,结果怎么都不对,发现dp[1]应该等于0,这样dp[2]才能正确计算。那为什么输出结果要+1呢?我晕了。

#include <iostream>
using namespace std;
int main()
{
    int dp[1000];
    dp[1] = 0;
    int N, M;
    cin >> N >> M;
    for (int i = 2; i <= N; i++)
    {
        dp[i] = (dp[i - 1] + M) % i;
    }
    cout << dp[N] + 1;
    system("pause");
    return 0;
}

单值dp可以不用数组存:

#include <iostream>
using namespace std;
int main()
{
    int n,m, f = 0;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) f = (f + m) % i;
    cout << f + 1 << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值