约瑟夫问题是个有名的问题: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有关系吗?我们来看一个具体的例子:
- 开始的时候,大家的序号是1234512345,如果变成123456789……这样的序列,那么让新序列号mod n即可得到正确序号,如上图我写的1-8
- 第一轮3死了,从4继续报数,报4个数所以在7这里停下。
- 第二轮4567号报数,谁会赢呢?想想1234号报数,若第二个人赢(2号赢),4567号报数也是第二个人赢(5号)。 那么就得到一个映射关系,若4人(1234号)报数x赢,则4567号报数x+3赢。但是这个x+3有可能大于5,例如7号赢,那么mod5得2也就是2号赢。
再来看抽象的例子:n人玩报k死。
- 第一轮k死了,从k+1往后报n-1个数,报到n+k-1正好n-1个数
- 那么从k+1号报数到n+k-1号中,谁会赢呢?
- 假设我们知道了从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[n−1]+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;
}