您的位置:首页 > 其它

论约瑟夫问题

2015-10-19 21:36 316 查看
有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时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(太高端了!!!)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: