论约瑟夫问题

有编号从1NN个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。

现在给定N,M,求N个小朋友的出圈顺序。


这是一道经典入门的题目。题目不是很难,用一些奇奇怪怪的模拟就可以了


以下就是最裸的模拟

#include <cstdio>
#define MAXN 100000
using namespace std;
bool hash[MAXN + 5];
int n, m;
int main()
{
	int p = 0;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= m; ++j)
		{
			++p;
			if (p == n + 1)
				p = 1;
			while (hash[p] == 1)
			{
				++p;
				if (p == n + 1)
					p = 1;
			}
		}
		hash[p] = 1;
		printf("%d ", p);
	}
	return 0;
}


然而,这种模拟实在是......并不想说什么...



然而codevs的数据范围看穿了一切




突然,我们有了另一种想法,这种想法十分神奇

#include <cstdio>
#define MAXN 100000
using namespace std;
int a[MAXN + 5];
int n, m;
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		a[i] = i;
	int p = m % n;
	if (p == 0)
		p = n;
	for (int i = 1; i < n; ++i)
	{
		printf("%d ", a[p]);
		for (int j = p; j <= n - i; ++j)
			a[j] = a[j + 1];
		p = (p + m - 1) % (n - i);
		if (p == 0)
			p = n - i;
	}
	printf("%d ", a[1]);
	return 0;
}
其实就是把挑出来的小朋友的位置挤掉~然而




P党乱入~~~

var a:array[1..1000000]of longint;
    k,w,i,j,n,m:longint;
begin
  readln(n,m);
  k:=1;
  for i:=1 to n do p[i]:=i;
    for i:=n downto 2 do
    begin
      k:=(k+m-1)mod i;
      if (k=0) then
        k:=i;
      w:=a[k];
      move(a[k+1],a[k],(i-k)*2);
      a[i]:=w;
    end;
  for i:=n downto 1 do write(a[i],' ');
end.</span>
这一个神奇的move函数完成了上面那个程序的一个o(m),而且使用的是库,比直接去弄要快。

然而还是只能对5个点,我写的太丑了。。。(其他的都是错误答案)希望有大神能写一写

其实在c++语言中,memmove函数也可以执行类似的功能,请有兴趣的OIer去写一写。博主太懒。。。(其实是太弱)



但是我自己把数据开到了(n <= 1000000, m <= 1000000),这显然......



继续冥思苦想,突然发现了好像是可以用线段树去做,好像时间复杂度可以降很多(萌萌哒)

#include <cstdio>
#define MAXN 10000000
using namespace std;
int n, m;
struct node
{
    int l, r, len;
}tree[MAXN + 5];
inline void build(int l, int r, int k)
{
    tree[k].l = l, tree[k].r = r, tree[k].len = r - l + 1;
    if (l == r)
    	return ;
	int mid = (l + r) / 2;
    build(l, mid, k * 2);
    build(mid + 1, r, k * 2 + 1);
}
inline void add(int i, int ord)
{
    --tree[i].len;
    if (tree[i].l == tree[i].r)
    {
        printf("%d ", tree[i].l);
        return ;
    }
    if (prd  <= tree[i].len)
        add(i * 2, ord);
    else 
        add(i * 2 + 1, ord - tree[i * 2].len);
}
int main()
{
    int p = 1;
    scanf("%d%d", &n, &m);
    build(1, n, 1);
    for(int i = n; i >= 1; --i)
    {
        p = (p + m - 1) % i;
        if (p == 0)
            p = i;
        add(1, p);
    }
    return 0;
}
然而这份segment tree还是十分好看的。


一百万都瞬间过掉了,太快了!!!



然而,某大神更加神,因为ta的线段树常数似乎要小一些(雾)。

#include<stdio.h>
int n,m,k,v;
struct Trees{
    int l,r,sum;
}t[12000001];
void Get_tree(int L,int R,int k)
{
    t[k].l=L; t[k].r=R; t[k].sum=R-L+1;
    if(L==R) return;
    Get_tree(L,(L+R)>>1,k<<1);
    Get_tree(((L+R)>>1)+1,R,(k<<1)+1);
}
void Move(int k,int w)
{
    t[k].sum--;
    if(t[k].l==t[k].r){ printf("%d ",t[k].l); return; }
    if(t[k<<1].sum>=w) Move(k<<1,w);
    else Move((k<<1)+1,w-t[k<<1].sum);
}
main()
{
    scanf("%d %d",&n,&m);
    Get_tree(1,n,1);
    k=n;
    while(k)
    {
        v=(v-1+m)%k;
        Move(1,v+1);
        k--;
    }
}

看起来很强的样子!!!!!



但是有一本叫具体数学的书上告诉了我们一种全新的方法!!!

然而本人见识短浅,并看不懂具体数学上面的各种证明,我给出几个高大上的式子吧!!!


(听说使用归纳假设法推(大雾))

加入对这个感兴趣的,可以看看这位博主  http://blog.sina.com.cn/s/blog_7cfbb10f0100qyn0.html(太高端了!!!)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值