约瑟夫

题目描述
nn个人站成一圈,从某个人开始数数,每次数到mm的人就被杀掉,然后下一个人重新开始数,直到最后只剩一个人。现在有一圈人,kk个好人站在一起,kk个坏人站在一起。从第一个好人开始数数。你要确定一个最小的mm,使得在第一个好人被杀死前,kk个坏人先被杀死。

感谢yh大神指出样例数据的错误。

输入格式
一个k(0<k<14)k(0<k<14)

输出格式
一个mm

输入输出样例
输入 #1 复制
3
输出 #1 复制
5
输入 #2 复制
4
输出 #2 复制
30
说明/提示
0<k<140<k<14

解题思路:
这道题也是很巧妙的,首先我们用一个动态数组储存是好人还是坏人,好人是1坏人是2,然后我们便从1 0 0开始循环寻找符合条件的m,每当第一个被选中了坏人还没都出去,我们就结束当前进行下个循环,否则就一直找。
AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
const double N = 1e6+10;
const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
#define ll long long
#define CL(a,b) memset(a,b,sizeof(a))
#define MAXN 100010
int k , i , sum = 0, num=0;
vector<int>a;
bool tle(int i , int sum , int num)
{
	if(sum == k)
	  return true;
	if(a[(num-1+i)%(2*k-sum)] == 1)
	  return false;
	if(a[(num+i-1)%(2*k-sum)] == 0)
	{
		a.pop_back() ;
		return tle(i,sum+1,(num+i-1)%(2*k-sum));
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> k;
	for(i = 1 ; i <= 2*k ; i++)
	{
		if(i<=k)
		  a.push_back(1);
		else
		  a.push_back(0);
	}
	for(i = k+1;;i++)
	{
		if(tle(i,0,0))
		{
			cout << i;
			break;
		}
	}
	return 0;
}
//相当于num退回了一个位置再走i步
 // 2*k-sum表示总人数-淘汰过的人数

偶然得知了大佬的推算方法,让我一脸懵逼。
首先我们把这n个人的序号编号从0~n-1(理由很简单,由于m是可能大于n的,而当m大于等于n时,那么第一个出列的人编号是m%n,而m%n是可能等于0的,这样编号的话能够简化后续出列的过程),当数到m-1的那个人出列,因此我们编号完成之后,开始分析出列的过程:
第一次出列:
一开始的时候,所有人的编号排成序列的模式即为:
0,1,2,3,4,5…n-2,n-1
那么第一次出列的人的编号则是(m-1)%n1,那么在第一个人出列之后,从他的下一个人又开始从0开始报数,为了方便我们设k1 = m%n1(n1为当前序列的总人数)那么在第一个人出列之后,k1则是下一次新的编号序列的首位元素,那么我们得到的新的编号序列为:
k1,k1+1,k1+2,k1+3…n-2,n-1,0,1,2…k1-3,k1-2 (k1-1第一次已出列)
那么在这个新的序列中,第一个人依旧是从0开始报数,那么在这个新的序列中,每个人报的相应数字为:
0,1,2,3…n-2
那么第二次每个人报的相应数字与第一次时自己相应的编号对应起来的关系则为:
0 --> k1
1 --> k1+1
2 --> k1+2

n-2 —> (k1+n-2)%n1(n1为当前序列的总人数,因为是循环的序列,k1+n-1可能大于总人数)
那么这时我们要解决的问题就是n-1个人的报数问题(即n-1阶约瑟夫环的问题)
可能以上过程你还是觉得不太清晰,那么我们重复以上过程,继续推导剩余的n-1个人的约瑟夫环的问题:
那么在这剩下的n-1个人中,我们也可以为了方便,将这n-1个人编号为:
0,1,2,3,4…n-2
那么此时出列的人的编号则是(m-1) % n2(n2为当前序列的总人数),同样的我们设k2 = m % n2,那么在这个人出列了以后,序列重排,重排后新的编号序列为:
k2,k2+1,k2+2,k2+3…n-2,n-1,0,1,2…k2-3,k2-2 (k2-1第一次已出列)
那么在这个新的序列中,第一个人依旧是从1开始报数,那么在这个新的序列中,每个人报的相应数字为:
1,2,3,4…n-2
那么这样的话是不是又把问题转化成了n-2阶约瑟夫环的问题呢?
后面的过程与前两次的过程一模一样,那么递归处理下去,直到最后只剩下一个人的时候,便可以直接得出结果
当我们得到一个人的时候(即一阶约瑟夫环问题)的结果,那么我们是否能通过一阶约瑟夫环问题的结果,推导出二阶约瑟夫环的结果呢?
借助上面的分析过程,我们知道,当在解决n阶约瑟夫环问题时,序号为k1的人出列后,剩下的n-1个人又重新组成了一个n-1阶的约瑟夫环,那么
假如得到了这个n-1阶约瑟夫环问题的结果为ans(即最后一个出列的人编号为ans),那么我们通过上述分析过程,可以知道,n阶约瑟夫环的结果
(ans + k)%n(n为当前序列的总人数),而k = m%n
则有:
n阶约瑟夫环的结果

(ans + m % n)%n,那么我们还可以将该式进行一下简单的化简:

当m<n时,易得上式可化简为:(ans + m)% n

而当m>=n时,那么上式则化简为:(ans % n + m%n%n)% n
即为:(ans % n + m%n)% n
而 (ans + m)% n = (ans % n + m%n)% n
因此得证
(ans + m % n)%n = (ans + m)% n
这样的话,我们就得到了递推公式,由于编号是从0开始的,那么我们可以令
f[1] = 0; //当一个人的时候,出队人员编号为0
f[n] = (f[n-1] + m)%n //m表示每次数到该数的人出列,n表示当前序列的总人数
而我们只需要得到第n次出列的结果即可,那么不需要另外声明数组保存数据,只需要直接一个for循环求得n阶约瑟夫环问题的结果即可
由于往往现实生活中编号是从1-n,那么我们把最后的结果加1即可。
也就是说,不需要我们进行什么栈的模拟,直接就一个公式就完事了。

#include <cstdio>
#include <cstring>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <algorithm>
#include<bits/stdc++.h>
using namespace std;
const double N = 1e6+10;
const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
const inline int read(){
    int k = 0, f = 1; char c = getchar();
    for(;!isdigit(c); c = getchar())
        if(c == '-') f = -1;
    for(;isdigit(c); c = getchar())
        k = k * 10 + c - '0';
    return k * f;
}
#define ll long long
#define CL(a,b) memset(a,b,sizeof(a))
#define MAXN 100010
int main()
{
	int n , k;
	cin >> n >> k;
	int s = 0;
	for(int i = 2 ; i <= n ; i++)
	{
		s = (s+k)%i;
	}
	cout << s+1 << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值