约瑟夫环
Posted zzq
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了约瑟夫环相关的知识,希望对你有一定的参考价值。
最近又看到了当时困惑自己很久的约瑟夫,本质上来说有点类似于dp,推导出f(n)与f(n-1)的转移关系递推求解。
下面是最经典的约瑟夫问题,什么?链表模拟? 不存在的>_<
https://vjudge.net/problem/51Nod-1073
首先,将n个人编号为: 0,1,2,3........k-1,k,k+1........n-1,
第一轮结束之后第k个人,也就是k-1将出列,剩下的人: 0,1,2,3.........k-2, k, k+1.......n-1
下一轮从k开始我们进行重新编号,将上面的编号为: n-k,n-k+1,n-k+2,n-k+3.............., 0,.1,.........n-k-1,
我们如果知道了这一轮存活下来的人对应的编号是y,那么他本来的编号就是 x=(y+k)%n
由此引出递推式 f(n)=(f(n-1)+k)%n | f(1)=0
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int main() 5 { 6 int n,k,i,j; 7 while(cin>>n>>k){ 8 int x=0; 9 for(i=2;i<=n;++i) 10 x=(x+k)%i; 11 cout<<x+1<<endl; 12 } 13 return 0; 14 }
有关这个问题的变形问题也是众多,下面我挑了一些来练习,
https://vjudge.net/problem/UVA-1394
题意,给出n,k,m表示,第一次先出队第m个人,然后从后面的人开始数到k的出列接着从1开始数,
和原来的有了一点变化就是不是从第一个人开始数了,而是先出队第m然后从m+1开始,不过只有第一轮的和后面的公式不一样我们只要算出f(n-1)再推f(n)就好了。
我们将第m人出列后从新对余下的(n-1)人编号,f(n-1)=(f(n-2)+k)%(n-1); 设x=f(n-1),则最后的答案 ans=(x+m)%n+1;
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int main() 5 { 6 int n,k,i,j,m; 7 while(cin>>n>>k>>m&&(n+k+m)){ 8 int x=0; 9 for(i=2;i<n;++i) 10 x=(x+k)%i; 11 x=(m+x)%n; 12 if(x<0) x+=n; 13 cout<<x+1<<endl; 14 } 15 return 0; 16 }
http://acm.hdu.edu.cn/showproblem.php?pid=2211
这个就是每次出队是k倍数的人,找到队尾之后将再次从队首开始数1,与约瑟夫有些不同,但不变的是公式推倒的过程。
想要知道f(n),我们不妨先假设得到了x=f(n-1),这里的n表示是第几轮从头开始,我们如果将这个x转为原本的编号呢,如果我们知道他前面死了几个人的话,
直接让x加上这个人数就好了,因为数到k就死一个,也就是说x前面有几个(k-1)就表示上一轮x前面淘汰的人,加上就好,最后边界f(k,k)=k
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int dfs(int n,int k) 5 { 6 if(n==k) return k; 7 int x=dfs(n-n/k,k); 8 return x+(x-1)/(k-1); 9 } 10 int main() 11 { 12 int n,k,i,j,m; 13 cin>>m; 14 while(m--){ 15 cin>>n>>k; 16 cout<<dfs(n,k)<<endl; 17 } 18 return 0; 19 }
以上是关于约瑟夫环的主要内容,如果未能解决你的问题,请参考以下文章