洛谷 CF792B - 题解

1. 问题描述

n 个孩子在玩一个游戏。 孩子们站成一圈,按照顺时针顺序分别被标号为 1 到 n。开始游戏时,第一个孩子成为领导。 游戏进行 k 轮。 在第 i 轮中,领导会从他顺时针方向下一个孩子开始数 ai 个孩子。最后数到的那个孩子出局,再下一个孩子成为新的领导。
举个例子, 现在圈内还剩 [8, 10, 13, 14, 16]5个孩子,领导编号为 13 , ai = 12。那么出局的孩子为 16 。第 8 个孩子成为下一个领导。
你需要写一个代码模拟这个过程,求出每轮比赛出局的孩子。
第一行包含两个整数 n 和 k (2 ≤ n ≤ 100, 1 ≤ k ≤ n - 1).
第二行包含 k 个整数 a1, a2, ..., ak (1 ≤ ai ≤ 1e9).
输出 k 个整数,第 i 个整数表示第 i轮出局的孩子。

输入输出样例

输入 #1

7 5
10 4 11 4 1

输出 #1

4 2 5 6 1 

输入 #2

3 2
2 5

输出 #2

3 2 

2.思路分析

由题意会有孩子出局,所以存储孩子的标号应该是动态的,且本游戏数到末尾又会回到第一个数继续数,因此我们可以采用单向循环链表。链表的结构体包括两个内容:一是int型来存储标号,二是指向本结构体的指针。当数到对应结点时,则删除此结点,继续数下一个,以此类推。
 

3.算法描述

(1)键盘输入n和k,表示孩子的个数以及总轮数
(2)创建单向循环链表并标号1到n
(3)创建数组ai,来存储每轮要数的孩子的个数,由于个数范围较大(1-1e9),因此可以定义为long long类型的数组
(4)循环遍历每一个ai,利用取模即ai%n(结果为要数的孩子数)来减少循环的次数,就不用数那么多轮了
(5)当找到要删掉(即要踢出局的人)时,把结点删掉,并将左右结点连接,继续数下一轮,直到k轮全部数完


注意:在删除结点时,要删除的结点要分首结点、尾结点和其他结点三部分分别进行讨论,具体情况具体处理

4.代码实现

#include<iostream>
using namespace std;
typedef struct node{
    int a;
    struct node *next;
}Node;
int main()
{
    Node *head,*rear,*last,*q;
    int n,k,i;
    cin>>n>>k;
    Node *p;
    head=NULL;  //建立动态链表
    for(i=1;i<=n;i++){
        p=new Node;
        p->a=i;
        if(head){
            rear->next=p;
            rear=p;
        }else{      //没有空结点的链表
            head=p;
            rear=head;
        }
    }
    rear->next=head;  //创建单向循环链表
    long long ai[105];  //由于ai范围在1-1e9较大
    for(i=0;i<k;i++){
        cin>>ai[i];
    }
    last=head;  //首结点为第一轮的领导
    q=rear;
    /*注意此处q不应该赋值为NULL,应该赋值为尾结点的地址,
    否则当第一轮要删除的结点为头结点时,无法对尾结点的next进行赋值为head,
    构成循环链表,且当赋值为NULL时,q->next程序会异常 */
    for(i=0;i<k;i++){
        ai[i]%=n;  //减少下面循环的次数,结果为要继续数的人数
        for(;ai[i]!=0;ai[i]--){
            q=last; //last最终指向要删除的结点,而q是用来连接左右结点的
            last=last->next;
        }
        cout<<last->a<<' ';
        n--;
        if(i==k-1)return 0;  //当i==k-1即说明此时是最后一轮了,无需再进行删除结点的操作
        //删除结点分三种情况讨论:头结点、尾结点、其他结点
        if(last==head){
            head=last->next;
            q->next=head;   //让尾部指向头结点
        }else if(last->next==head){
            q->next=head;  //让尾部指向头节点
        }else{
            q->next=last->next;  //让被删除结点的左右结点连接
        }
        delete(last);   //清除要删除的结点
        last=q->next;   //指向被删除结点的下一个(即领导)
    }
    return 0;
}

5. 程序执行结果

6.分析与总结

该程序的平均时间复杂度为O(k*n/2),其中k为轮数,n为小孩的数量
注意:
(1)对链表的创立与删除要依据具体情况具体分析
(2)对于数据范围较大的可以采用long long甚至unsigned long long

ps:此题也可以用数组的方式,即创建一个数组,下标表示小孩的标号,从1开始到n,舍弃0;先初始化数组为0,此后每次出局便赋值为1,在数人的时候跳过那些1即可,较为容易实现,不多介绍,下面附上代码:

#include<iostream>
using namespace std;
int main()
{
    long long ai[100];
    int test[105]={0};
    int n,k,i,j,x;
    cin>>n>>k;
    for(i=0;i<k;i++){
        cin>>ai[i];
    }
    int count=0;
    int t=n;
    for(i=0,j=1;i<k;i++){
        if(count==-1){
            if(j==n)j=0;
            for(x=j+1;test[x]==1;x++){
                if(x>=n)x=0;
            }
            count=0;
            j=x;
        }
        ai[i]%=t;
        while(count<ai[i]){
            if(j+1<=n&&test[j+1]!=1)count++;
            if(j<n)j++;
            else j=0;
        }
        cout<<j<<' ';
        test[j]=1;
        count=-1;
        t--;
    }
    return 0;
}

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值