題目描述 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(N)的算法
對于求順序的 有O(NlogN)的算法
用線段樹實作
因為線段樹是維護區間資訊的資料結構
主要思想就是 根節點0 1 表示是否在圈内 維護一個sumv的線段樹
目前出圈人是第pos人 下一次是第pos+n-1人 因為剛才pos出去了 是以要-1 為了防止它大于sumv[1] 是以要%sumv[1] 又為了防止它是第0個人 是以特判一下
注意是人不是編号 而線段樹維護的就是在圈内的人的數量
是以可以遞歸找到是哪個根節點 然後輸出它對應的編号
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int lim=30033;
int sumv[lim<<2];
int m,n,want,pos;
int num[lim<<2],rank;
void pushup(int o){sumv[o]=sumv[o<<1]+sumv[o<<1|1];}
void build(int o,int l,int r)
{
if(l==r)
{
sumv[o]=1;
rank++;
num[o]=rank;
return;
}
int m=(l+r)>>1;
build(o<<1,l,m);
build(o<<1|1,m+1,r);
pushup(o);
}
void del(int o,int l,int r)
{
if(l==r)
{
sumv[o]=0;
cout<<num[o]<<' ';
return;
}
int m=(l+r)>>1;
if(want<=sumv[o<<1])del(o<<1,l,m);
else
{
want-=sumv[o<<1];
del(o<<1|1,m+1,r);
}
pushup(o);
}
int main()
{
scanf("%d%d",&m,&n);
n%=m;
if(n==0)n=m;
build(1,1,m);
pos=1;
while(sumv[1])
{
pos=(pos+n-1)%sumv[1];
if(pos==0)pos=sumv[1];
want=pos;
del(1,1,m);
}
return 0;
}