论约瑟夫问题
2015-10-19 21:36
316 查看
有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。
现在给定N,M,求N个小朋友的出圈顺序。
这是一道经典入门的题目。题目不是很难,用一些奇奇怪怪的模拟就可以了
以下就是最裸的模拟
然而,这种模拟实在是......并不想说什么...
然而codevs的数据范围看穿了一切
突然,我们有了另一种想法,这种想法十分神奇
P党乱入~~~
然而还是只能对5个点,我写的太丑了。。。(其他的都是错误答案)希望有大神能写一写
其实在c++语言中,memmove函数也可以执行类似的功能,请有兴趣的OIer去写一写。博主太懒。。。(其实是太弱)
但是我自己把数据开到了(n <= 1000000, m <= 1000000),这显然......
继续冥思苦想,突然发现了好像是可以用线段树去做,好像时间复杂度可以降很多(萌萌哒)
一百万都瞬间过掉了,太快了!!!
然而,某大神更加神,因为ta的线段树常数似乎要小一些(雾)。
看起来很强的样子!!!!!
但是有一本叫具体数学的书上告诉了我们一种全新的方法!!!
然而本人见识短浅,并看不懂具体数学上面的各种证明,我给出几个高大上的式子吧!!!
(听说使用归纳假设法推(大雾))
加入对这个感兴趣的,可以看看这位博主 http://blog.sina.com.cn/s/blog_7cfbb10f0100qyn0.html(太高端了!!!)
现在给定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(太高端了!!!)
相关文章推荐
- SQLServer单表查询
- java调戏人小游戏
- value vecter和 vector 的区别
- STM32F1系列的各单片机的比对分析
- 铜梁视窗运营总结【一】
- 最长公共子序列(Longest Common Subsequence)
- 5-18 二分法求多项式单根
- Java中的byte转string
- 数据结构例程——二叉树遍历的非递归算法
- 分盐问题
- 截屏实现
- ImageView属性动画--背景缓缓移动
- 四校联训Round6心得体会
- unix的5种I/O
- thinkphp模型
- ural 1223. Chernobyl’ Eagle on a Roof
- 数字证书应用改造需求调研-2015-10-19
- jmp address windows hook
- 进程的组织
- 关于R中的mode()和class()的区别