【数据结构】P1996 约瑟夫问题

【题目链接】

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——list

【解法二】

利用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 }
STL—queue

【解法三】

手工实现链表

 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 }
权值树状数组

猜你喜欢

转载自www.cnblogs.com/Osea/p/11371750.html