【题目链接】
https://www.luogu.org/problem/P1996
题目描述
n个人(n<=100)围成一圈,从第一个人开始报数,数到m的人出列,再由下一个人重新从1开始报数,数到m的人再出圈,……
依次类推,直到所有的人都出圈,请输出依次出圈人的编号.
输入格式
n m
输出格式
出圈的编号
输入输出样例
输入 #1
10 3
输出 #1
3 6 9 2 7 1 8 5 10 4
说明/提示
100 ≤ m,n ≤ 100
【题解】
问题其实并不困难,但是目的就是利用题目来锻炼自己的数据结构。
给出一题五解的做法。
【解法一】
利用STL里面的list,注意指针到了链表尾部要指回链表的头部。

1 #include<bits/stdc++.h>
2 using namespace std;
3 typedef long long ll;
4 int n,m;
5
6 int main()
7 {
8 list<int> List ;
9 scanf("%d%d",&n,&m);
10 for(int i=1;i<=n;i++) List.push_back(i);
11 list<int> ::iterator it = List.begin(),tmp;
12
13 for(int i=1;i<=n;i++){
14 for(int j=0;j<m-1;j++){
15 it++;
16 if( it == List.end() )
17 it = List.begin() ;
18
19 }
20
21 printf("%d ",*it);
22 tmp = it ;
23 it++;
24 if( it == List.end() ) it = List.begin() ;
25 List.erase(tmp);
26 }
27 return 0;
28 }
【解法二】
利用STL里面的queue,实现该过程,从队头出来,从队尾插入。

1 #include<cstdio>
2 #include<queue>
3 using namespace std;
4 int main()
5 {
6 int n,m;
7 queue<int> Q;
8 scanf("%d%d",&n,&m);
9 for(int i=1;i<=n;i++){
10 Q.push(i);
11 }
12 for(int i=1;i<=n;i++){
13 int cur = Q.front();
14 Q.pop();
15 for(int j=0;j<m-1;j++){
16
17 Q.push(cur);
18 cur = Q.front() ;
19 Q.pop();
20 }
21 printf("%d%c",cur,i==n?'\n':' ');
22 }
23 return 0;
24 }
【解法三】
手工实现链表

1 #include<cstdio>
2 #include<queue>
3 #include<cstdlib>
4 using namespace std;
5 const int N = 1e5+10;
6
7 typedef struct Node{
8 int val ;
9 Node * next;
10 }Node;
11 Node *head , *tail , *tmp, *p ;
12
13 int main()
14 {
15 int n,m;
16 head = new Node ;
17 head -> next = NULL ;
18 tail = head ;
19 scanf("%d%d",&n,&m);
20 for(int i=1;i<=n;i++){
21 p = new Node ;
22 p -> val = i ;
23 p -> next = NULL ;
24 tail -> next = p ;
25 tail = p ;
26 }
27
28 p = head -> next ;
29 tail -> next = head -> next;
30
31 for(int i=1;i<=n;i++){
32
33 for(int j=0;j<m-2;j++){
34 p = p->next;
35 }
36 printf("%d ",p->next->val);
37 tmp = p -> next ;
38 p -> next = tmp -> next ;
39 p = p -> next ;
40 free(tmp);
41 }
42 return 0;
43 }
【解法四】
手工实现队列

1 #include<cstdio>
2 #include<queue>
3 using namespace std;
4 const int N = 1e5+10;
5 int main()
6 {
7 int n,m;
8 int Head = 1 , Tail = 0 ;
9 int Q[N];
10 scanf("%d%d",&n,&m);
11 for(int i=1;i<=n;i++){
12 Q[++Tail] = i ;
13 }
14 for(int i=1;i<=n;i++){
15 int cur = Q[Head++];
16 for(int j=0;j<m-1;j++){
17 Q[++Tail] = cur ;
18 cur = Q[Head++];
19 }
20 printf("%d%c",cur,i==n?'\n':' ');
21 }
22 return 0;
23 }
【解法五】
这个题目最正解的做法是权值线段树,权值树状数组。

1 #include<iostream>
2 #include<cstdio>
3
4 using namespace std;
5
6 const int N=100;
7
8 int n,m;
9
10 struct Stree
11 {
12 int l,r;
13 int dat;
14 }t[N<<2];
15 //结构体
16
17 //建树
18 void build(int p,int l,int r)
19 {
20 t[p].l=l;t[p].r=r;
21 if(l==r)
22 {
23 t[p].dat=1;
24 //初始化为1,表示这里是有人的
25 return;
26 }
27 int mid=(l+r)>>1;
28 build(p<<1,l,mid);
29 build(p<<1|1,mid+1,r);
30 t[p].dat=t[p<<1].dat+t[p<<1|1].dat;
31 }
32
33 //把 x 踢出去
34 void change(int p,int x)
35 {
36 if(t[p].l==t[p].r)
37 {
38 t[p].dat=0;
39 return;
40 }
41 int mid=(t[p].l+t[p].r)>>1;
42 if(x<=mid) change(p<<1,x);
43 else change(p<<1|1,x);
44 t[p].dat=t[p<<1].dat+t[p<<1|1].dat;
45 }
46
47 //查询 x 的位置
48 int query(int p,int x)
49 {
50 if(t[p].l==t[p].r)
51 return t[p].l;
52 //如果左边的剩余位置小于这个编号,那就在右边区域查找左边区域放不下的
53 if(x>t[p<<1].dat) return query(p<<1|1,x-t[p<<1].dat);
54 else return query(p<<1,x);
55 }
56
57 int main()
58 {
59 scanf("%d%d",&n,&m);
60 if(n==0) return 0;
61 build(1,1,n);
62 int pos=1;
63 while(n)
64 {
65 pos=(pos+m-2)%t[1].dat+1;//t[1].dat即剩余总人数
66 //先给 pos-1, 避免出现mod 完变成0的情况,mod完之后在 +1
67 //处理位置
68 // if(pos==0) pos=t[1].dat;
69 int qwq=query(1,pos);
70 //查寻当前这个人的位置
71 cout<<qwq<<" ";
72 //输出
73 change(1,qwq);
74 //踢出队伍
75 n--;
76 }
77
78 return 0;
79 }
80 //By Yfengzi

1 #include<iostream>
2 #include<cstdio>
3 using namespace std;
4
5 const int maxn=3e4+10;
6 int n,m,maxx;
7 int bit[maxn];
8
9 inline int lowbit(int x)
10 {
11 return x&-x;
12 }
13 inline void add(int pos,int x)
14 {
15 for(int i=pos;i<=maxx;i+=lowbit(i))bit[i]+=x;
16 }
17 inline int find_kth(int k)
18 {
19 int ans=0,now=0;
20 for(int i=15;i>=0;i--)
21 {
22 ans+=(1<<i);
23 if(ans>maxx||bit[ans]+now>=k)ans-=(1<<i);
24 else now+=bit[ans];
25 }
26 return ans+1;
27 }
28
29 int main()
30 {
31 scanf("%d %d",&n,&m);
32 maxx=n; //这里因为n后面会改变,所以先记录一下n的值。
33 for(int i=1;i<=n;i++)bit[i]=lowbit(i);//这里完全等价于add(i,1),因为一开始都是1,所以bit[i]=i-(i-lowbit(i)+1)+1=lowbit(i)
34 int now=1;//从1开始
35 while(n)
36 {
37 now=(now-1+m-1)%n+1;//这里是小细节,本来的式子应该是(now+m-1)%n的,但是考虑如果只剩下2个元素,而我们当前要找的就是第二个元素呢?直接模就是0了,所以用一个+1 -1 的小操作更改取模运算的值域,这样就可以取到n的值了,而对别的无影响
38 int ans=find_kth(now);//找kth
39 add(ans,-1);//把这个人删除
40 printf("%d ",ans);
41 n--;
42 }
43 return 0;
44 }
