类型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); } }
hdu 2925
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 99000000 const int ten[7]={1,10,100,1000,10000,100000,1000000}; struct aaa { int val,id,ans; }al[10000]; bool cmp_val(aaa a1,aaa a2) { return a1.val<a2.val; } bool cmp_id(aaa a1,aaa a2) { return a1.id<a2.id; } int main() { // freopen("input.txt","r",stdin); char str[5]; int n=0; while (scanf("%s",str),str[0]+str[1]+str[3]-‘0‘*3) { int i,x; x=((str[0]-‘0‘)*10+str[1]-‘0‘)*ten[str[3]-‘0‘]; al[n++].val=x; al[n-1].id=n-1; } sort(al,al+n,cmp_val); int now=0; int x,y,z; int i; x=0; while (now<n && al[now].val==1) al[now].ans=x+1; for (i=2;i<=MAXN;++i) { x=(x+2)>=i?(x+2-i):(x+2); while (now<n && al[now].val==i) al[now++].ans=x+1; } sort(al,al+n,cmp_id); for (i=0;i<n;i++) { printf("%d\n",al[i].ans); } }
poj 1781 O(n)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 99000000 const int ten[7]={1,10,100,1000,10000,100000,1000000}; int main() { freopen("input.txt","r",stdin); char str[5]; int n=0; int x; while (scanf("%s",str),str[0]+str[1]+str[3]-‘0‘*3) { x=((str[0]-‘0‘)*10+str[1]-‘0‘)*ten[str[3]-‘0‘]; for (int i=30;i>=0;i--) { if (x&(1<<i)) { printf("%d\n",(((x^(1<<i))<<1)+1)); break; } } } }
poj 1781 O(log)