@RabbitHu
2017-08-21T17:48:05.000000Z
字数 1442
阅读 1868
题解
题目描述 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(mn),非常慢,像这道题一定会爆掉的!
但是有了线段树以后我们可以得到一个非常 妙~~~ 的解法,它的复杂度是 O(nlogn)。
首先我们考虑,现在有一排圆滚滚的熊孩子
OOOOOOOOO
把他们从0开始编号:
OOOOOOOOO
012345678
假设我们让数到3的熊孩子退出,那么第一个退出的是当前队伍里的第三个——也就是2号熊孩子,队伍变成这样:
OOOOOOOO
01345678
第二个退出的熊孩子是当前队伍里往下数三个——也就是5号熊孩子。
以此类推(当然,数到队伍末尾的时候要模当前队伍的长度),显然我们每次都知道当前队伍中第几个会退出。
那么问题来了:我们需要知道当前队伍中的第p个人在初始队伍中的编号,并输出。
这时候我们就可以用线段树了。
这棵线段树非常简单:一个节点存储的是它所代表的区间[i,j]中,还没退出的人数。这里的i,j都是初始队伍中的编号。
那么:
[N]
|\
[2*N][2*N+1]
我们要找N代表的区间中第p个还在队伍中的熊孩子(从0开始数),要判断一下:
1.如果 p < data[2*N], 就在左儿子里找第p个;
2.如果 p >= data[2*N], 就在右儿子里找第 p-data[2*N] 个。
这样就可以找到啦。
#include <cstdio>
using namespace std;
int data[120005], n, m;
void build(int i, int l, int r){
if(l == r){
data[i] = 1;
return;
}
int mid = (l + r) >> 1;
build(i<<1, l, mid);
build(i<<1|1, mid+1, r);
data[i] = data[i<<1] + data[i<<1|1];
}
void dlt(int i, int p, int l, int r){
if(l == r){
printf("%d ", l+1);
data[i] = 0;
return;
}
int mid = (l + r) >> 1;
if(data[i << 1] > p) dlt(i << 1, p, l, mid);
else dlt(i << 1 | 1, p - data[i << 1], mid + 1, r);
data[i] = data[i<<1] + data[i<<1|1];
}
int main() {
scanf("%d%d", &n, &m);
int p = 0;
build(1, 0, n-1);
for(int q = 1; q <= n; q++){
p = (p + m - 1) % (n - q + 1);
dlt(1, p, 0, n - 1);
}
return 0;
}