wikioi-天梯-进入省队-线段树-1282:约瑟夫问题

来源:互联网 发布:淘宝云标签考试 编辑:程序博客网 时间:2024/05/16 01:20

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

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

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

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

5 3

3 1 5 2 4

类型:线段树  难度:3

题意:n个人围成一圈,顺时针编号1-n,给出一个m,从第1个人开始数,数到第m个人就出圈,然后从出圈的下一个人继续数m个。。。求出圈的顺序。

分析:用线段树存储每个区间中剩余的人数,即在圈内则置为1,出圈的置为0,用realm表示当前查找的是还在圈中的第几个人,即线段树(1,x)区间的和为realm,那么x就是这次出圈的人。例:若当前的序列为10110,realm=2,那么这次出圈的人是3。

计算realm:realm初始化为1,每次更新realm = (realm-2+m)%i+1,i=n,...,1,这个式子的解释:首先考虑(realm+m)%i为下一个查找realm,发现每次去掉一个人后,相当于从realm之前的一个人开始数,所以变成(realm-1+m)%i,但是%i的结果可能为0,所以在最后+1,前面再-1。

查找x满足(1,x)的和为realm,且x为1:类似二分查找,先查找线段树左子树的和,若小于等于realm,则x在左子树,递归查找;若大于realm,查找右子树,realm更新为realm-sum[left],直到查找区间的左右边界相等,即返回结果。

代码:

#include<iostream>#include<cstring>#include<cstdlib>#include<cstdio>using namespace std;#define lson l , m , rt << 1#define rson m + 1 , r , rt << 1 | 1const int maxn = 100010;int n,m;int sum[maxn<<2];void PushUP(int rt) {//把当前结点的信息更新到父结点 sum[rt] = sum[rt<<1] + sum[rt<<1|1];}void build(int l,int r,int rt) {//建立线段树if (l == r) {sum[rt] = 1;return ;}int m = (l + r) >> 1;build(lson);build(rson);PushUP(rt);}void update(int p,int v,int l,int r,int rt) {//单点增减if (l == r) {sum[rt] = v;return ;}int m = (l + r) >> 1;if (p <= m) update(p , v , lson);else update(p , v , rson);PushUP(rt);}int findpos(int len,int l,int r,int rt) {//区间求和if (l==r) {return l;}int left = rt<<1;int m = (l+r)>>1;if(len <= sum[left])        return findpos(len,lson);return findpos(len-sum[left],rson);}int main(){    scanf("%d%d",&n,&m);    build(1,n,1);    int realm = 1,now;    for(int i=n; i>0; i--)    {        realm = (realm-2+m)%i+1;        now = findpos(realm,1,n,1);        printf("%d ",now);        update(now,0,1,n,1);    }    printf("\n");}


 

0 0