codevs 1282 约瑟夫问题 线段树

题目描述 Description

有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。

现在给定N,M,求N个小朋友的出圈顺序。

输入描述 Input Description

唯一的一行包含两个整数N,M。(1<=N,M<=30000)

输出描述 Output Description

唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。

样例输入 Sample Input

5 3

样例输出 Sample Output

3 1 5 2 4

题解:

对于约瑟夫问题我们有很多种姿势去解决
对于求最后的位置有O(n)的
而对于每次的出队位置,可以用线段树O(nlogn)求解
对于这道题:
因为线段树是维护区间信息的数据结构
主要思想就是 根节点0 1 表示是否在圈内 维护一个sum的线段树
当前出圈人是第pos人 下一次是第pos+n-1人 因为刚才pos出去了 所以要-1  为了防止它大于sumv[1] 所以要%sum[1] 又为了防止它是第0个人 所以特判一下
注意是人不是编号  而线段树维护的就是在圈内的人的数量
所以可以递归找到是哪个根节点 然后输出它对应的编号

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxx = 30050;
int sum[maxx << 2];
int want, pos;
int num[maxx << 2], rank;
void build(int p, int l, int r)
{
	if (l == r)
	{
		sum[p] = 1;
		rank ++;
		num[p] = rank;
		return ;
	}
	int mid = (l + r) >> 1;
	build (p << 1, l, mid);
	build (p << 1 | 1, mid + 1, r);
	sum[p] = sum[p << 1] + sum[p << 1 | 1];
}
void del(int p, int l, int r)
{
	if (l == r)
	{
		sum[p] = 0;
		cout << num[p] << " ";
		return ;
	}
	int mid = (l + r) >> 1;
	if (want <= sum[p << 1])
		del(p << 1, l, mid);
	else
	{
		want -= sum[p << 1];
		del(p << 1 | 1, mid + 1, r);
	}
	sum[p] = sum[p << 1] + sum[p << 1 | 1];
}
int main()
{
	int n, m;
	cin>>n>>m;
	m %= n;
	if (m == 0)
		m = n;
	build(1, 1, n);
	pos = 1;
	while (sum[1])
	{
		pos = (pos + m - 1) % sum[1];
		if (pos == 0)
			pos = sum[1];
		want = pos;
		del (1, 1, n);
	}
	return 0;
}

 

上一篇
下一篇