首页 > 代码库 > 约瑟夫环

约瑟夫环

最近又看到了当时困惑自己很久的约瑟夫,本质上来说有点类似于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 }

 

约瑟夫环