【题目链接】
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?' ':' '); 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?' ':' '); 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 }