约瑟夫问题围圈报数
总共有n个小孩子 每次循环报到m个数字的时候就退出队列
如果采用普通的循环队列来模拟的话时间复杂度是o(n*m)
当n和m都很大的时候计算的时间就会很长
所以将环拆开成为一条线
然后利用线段树进行模拟 时间复杂度为o(n*logn)
这里利用了线段树统计区间上没有退出的孩子的个数
这样在没有退出的孩子中从左到右找到第p个孩子的时候就可以使用类似于二分的方法来查找 删除
例如 在segtree[index]所代表的区间中朝找没有删除的第p个数
如果p<=segtree[index<<1].num 则在左孩子节点中没有删除的点中查找第p个 元素
如果 p>segtree[index<<1].num 则在右孩子中没有删除的点中查找第
p-segtree[index<<1].num个元素
线段树中搜索的是在[segtree[index].l,segtree[index].r]区间范围内 从左到右第P个有效元素
而最大的区间其实就是[segtree[1].l,segtree[1].r]] 也就是[1,n]
问题的难点就在于怎么把要删除的元素的位置转换成在[1,n]区间内相对于没有删除元素的位置(因为线段树统计的是在这个区间上存在的元素 也就是没有删除元素的数量)
要删除元素的绝对位置:要删除元素在[1,n]区间内的下标
要删除元素的相对位置:要删除元素在[1,n]区间内没有删除元素中的位置(因为前面每删除一个元素 同一个元素 相对于没有删除元素的位置都在变化)
下面枚举几个要删除的元素的位置以找到其中的规律
绝对位置: m 2m 3m
相对位置 m 2m-1 3m-2
因为绝对位置是2m的点 在删除了m点后 它在未删除元素中的位置相当于他的绝对位置向前移动了一位
绝对位置是3m的点 在删除了m和2m点后 相对位置就相当于绝对位置向前移动了2位变成了3m-2
可以发现要删除点的相对位置每次都是增加m-1
#include "stdafx.h"
#include <iostream>
using namespace std;
typedef struct node {
int num;//[l,r]上面包含的节点的数目
int l;//左边界
int r;//右边界
}Node;
Node *segtree;//存储已经建好的线段树
/*
构造线段树
left为左边界
right为右边界
index为线段树节点的下标
*/
void build(int left, int right, int index)
{
segtree[index].l = left;
segtree[index].r = right;
if (right == left)//到了叶子节点返回 叶子节点包含一个元素
{
segtree[index].num = 1;
return;
}
int mid = (left + right) / 2;
build(left, mid, index << 1);//构建左子树
build(mid + 1, right, index << 1 | 1);//构建右子树
segtree[index].num = segtree[index << 1].num + segtree[index << 1 | 1].num;//回溯的时候利用子节点中有效数目来更新有效数目
}
/*
更新线段树
删除segtree[index]线段树节点所代表的区间中从左到右p位置元素
*/
int update(int p, int index)
{
if(segtree[index].r==segtree[index].l)//删除叶子节点
{
segtree[index].num =0;
return segtree[index].r;
}
segtree[index].num--;//删除这个区间中的元素导致区间中有效元素的个数减少
if (p <= segtree[index << 1].num)//如果p<=左子节点中有效元素的个数则从左叶子节点删除 否则从右子节点中删除
return update(p, index << 1);
else
return update(p - segtree[index << 1].num, index << 1 | 1);
}
int main()
{
int n, m;
cin >> n >> m;//n为开始时总的人数 m为循环报数的人数
segtree = (Node*)malloc(3 * n*sizeof(Node));
build(1, n, 1);//构建范围是从[1,n]的线段树
int seq = 1;
for (int i = 0; i < n; i++)
{
seq = (seq + m - 1) % segtree[1].num;
if (seq == 0)seq = segtree[1].num;
cout << update(seq, 1)<<" ";
}
system("pause");
}