线段树解决约瑟夫问题

约瑟夫问题围圈报数
总共有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");
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hebastast

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值