约瑟夫游戏

【问题描述】

YJC 很喜欢玩游戏,今天他决定和朋友们玩约瑟夫游戏。
约瑟夫游戏的规则是这样的:n 个人围成一圈,从 1 号开始依次报数,当报到 m 时,
报 1、2、…、m-1 的人出局,下一个人接着从 1 开始报,保证(n-1)是(m-1)的倍数。最后剩
的一个人获胜。
YJC 很想赢得游戏,但他太笨了,他想让你帮他算出自己应该站在哪个位置上。

【输入格式】

第一行包含两个整数 n 和 m,表示人数与数出的人数。

【输出格式】

输出一行,包含一个整数,表示站在几号位置上能获得胜利。

【输入输出样例】

joseph.in joseph.out
10 10 10

【数据说明】

对于 30%的数据,满足 2≤n≤1000;
对于 50%的数据,满足 2≤n≤1000000;
对于 100%的数据,满足 2≤m≤n<2^63 -1 且(n-1)是(m-1)的倍数。

【题解】

30%:直接模拟,每次暴力删除即可。时间复杂度 O(n 2 )。

50%:递推计算。设 F[n]表示 n 个人时最后剩下的人的编号。每
增 加 m-1 个 人 , 答 案 向 后 移 动 m 位 。 于 是 递 推 式 为
F[n]=F[n-m+1]%(n-m+1)+m,初始 F[1]=1。时间复杂度 O(n/m)。

100%:容(da)易(biao)看出,只有 F[m^a +m-1]=m,其余的 F[n]
都满足 F[n]=F[n-m+1]+m。于是设 n=m^a +(m-1) k (m^a < n ≤ m^a+1 ),那
么 F[n]=km。时间复杂度 O(log m n)。

这个规律很难找,不过我输n/m*m有50分是什么鬼(这说明这个规律适合大部分情况)。。。

试着弄出规律:(据说这规律只能手算,不能证)
这个代码看得懂一些

tr=(n-1)/(m-1)-1;
    long long a=m;
    while(a*m<=n)
    {
        tr-=a;
        a*=m;
    }
    tr*=m;
    if(tr==0)tr=n;
    cout<<tr;

这个程序实际上是倒着推,a代表当前人数,每乘m就是往前倒推一轮。而tr相当于要删去的(m-1)的个数,倒数第一轮删m^0个,第二轮m^1个,第三轮m^2个…到不够减的时候,幸存者即为那个余数。所以最后能求出当只有一个人时的当前轮数tr,而当前人的编号就是tr×m。
或者换种理解:
以n=13,m=3为例:
第一次筛后:3,6,9,12
同时除m:1,2,3,4
第二轮筛后:2
ans=2×m=6得解
a*m<=n用来判断是否前面还有一轮。
那个if用来特判m^2=n;
贴上递归变递推的程序。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define re register
#define il inline
#define fp(i,a,b) for(ll i=a;i<=b;i++)
#define fq(i,a,b) for(ll i=a;i>=b;i--)
using namespace std;
ll i;
il ll gi()
{  
  re ll x=0;
  re ll t=1;
  re char ch=getchar();
  while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
  if(ch=='-') t=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  return x*t;
}
int main()
{
    freopen("joseph.in","r",stdin);
    freopen("joseph.out","w",stdout);
    ll n,k;
    cin>>n>>k;
    for(i=1;i<n/k;i*=k);
    cout<<(n-i)/(k-1)*k<<endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}
单链表是一种常用的数据结构,可以用于实现约瑟夫游戏。下面是一个基于C语言的单链表实现约瑟夫游戏的代码: ```c #include <stdio.h> #include <stdlib.h> // 定义单向链表结构体 typedef struct node { int data; struct node *next; } Node, *LinkedList; // 创建单向链表 LinkedList createLinkedList(int n) { LinkedList head, p, q; int i; head = (LinkedList)malloc(sizeof(Node)); head->data = 1; head->next = NULL; q = head; for (i = 2; i <= n; i++) { p = (LinkedList)malloc(sizeof(Node)); p->data = i; p->next = NULL; q->next = p; q = q->next; } q->next = head; // 链接成环形链表 return head; } // 删除单向循环链表中第m个节点 LinkedList deleteNode(LinkedList head, int m) { LinkedList p = head, q; int i; while (p->next != p) { // 当链表中只剩一个节点时退出循环 for (i = 1; i < m - 1; i++) { p = p->next; } printf("出圈的人是:%d\n", p->next->data); q = p->next; p->next = q->next; free(q); p = p->next; } printf("最后留下的人是:%d\n", p->data); return p; } int main() { int n, m; LinkedList head; printf("请输入总人数n:"); scanf("%d", &n); printf("请输入每隔多少人出圈m:"); scanf("%d", &m); head = createLinkedList(n); deleteNode(head, m); return 0; } ``` 该代码中,createLinkedList函数用于创建一个包含n个节点的单向循环链表,并将链表的末尾节点连接到头节点,形成一个环形链表;deleteNode函数用于删除单向循环链表中每隔m个节点的节点,最终留下最后一个节点。 在main函数中,先读入总人数n和每隔多少人出圈m,然后调用createLinkedList函数创建链表并赋值给head变量,再调用deleteNode函数进行节点删除操作。最终输出最后留下的人的编号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值