类型1:约瑟夫问题原题:(http://acm.hdu.edu.cn/showproblem.php?pid=2925)
大体就是一圈人,数到m的退出,询问最后留下来的人。
这样的题是约瑟夫系列问题的基础,普通暴力做法O(n*m),不够优秀,可以通过数学方法优化至O(n),其所用的思路是将一个规模n的问题转移成规模n-1的问题。
公式形式推导都很简单:f(i)表示规模i的约瑟夫问题最后留下人的编号,f(i)=(f(i-1)+m)%i
类型2:规模变态版:(http://poj.org/problem?id=1781)
类型1限制只数1,2,即每次去掉偶数项,易推导f(i)=(f(i-1)+2)%i
然而数据时限卡O(n),通过《具体数学》上一种及其奇葩的推导,有一个O(log)解法,即f(i)=i循环右移(把二进制最高为的1放在最右面)
类型3:类型1变式:(http://poj.org/problem?id=1012)
2×n个人,通过约瑟夫方式间隔m杀人,使后n个坏人在前n个好人之前被杀的最小m。
引入Joseph递推公式,设有n个人,(0~n-1),间隔m, f(i) 表示当前子序列中第i轮要退出的那个人
则 f(0)=0;
f(i)=(f(i-1)+m-1)%(n-i+1);
这个公式我真不知道是咋来的。。。
反正假设我们知道这个公式,那么就可以从小大到枚举m,O(n)枚举。
类型4:模型转变
假设不再是圈,而是一个链,每次从没有死的第一个开始计数。
这道题我确实是结合标程与打表找规律做出来的。
打表每一轮死亡的人,设f(i,j)表示第i轮第j个死的人,我们可以将f(i,j)按照不同j值分类,这些都比较好想,
这道题难点在于第a个人所表示的f(i,j)=a,则f(i-1,j)=a-a/k-1,人从0计数。
上面做完就随便怎么都能做了。
现在暂时总结这么多,总之约瑟夫问题足够神奇,也足够恶心。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 1000000 int f[MAXN]; int main() { // freopen("input.txt","r",stdin); int n,m; while (scanf("%d%d",&n,&m),n+m) { f[1]=0; int t,x; for (int i=2;i<=n;i++) { f[i]=(f[i-1]+m%i)%i; } printf("%d %d %d\n",n,m,f[n]+1); } }