约瑟夫问题--五种变式

简单的约瑟夫问题:使用数组,模拟一个圈,每次数到m就标记为已读,并将num还原为0

接着从下一个人开始,如果到了最后一个人,就重新回到第一个人,继续开始寻找,如果没有被标记过num(数到的数字)++,直到所有人都退出圈子结束

源代码

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int main()
 6 {
 7     int n,m;
 8     bool vis[1000];
 9     int num=0,ans=0;
10     memset(vis,0,sizeof(vis));
11     scanf ("%d%d",&n,&m);
12     for (int i= 1;i <= n+1;i++)
13     {
14         if (i==n+1)
15             i=1;
16         if (vis[i]==0)
17             num++;
18         if(num==m)
19         {
20             ans++;
21             printf ("%d ",i);
22             vis[i]=1;
23             num=0;
24         }
25         if (ans==n)
26         break;
27     }
28     return 0;
29 }

 

 

已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
例如:n = 9, k = 1, m = 5
【解答】
出局人的顺序为5, 1, 7, 4, 3, 6, 9, 2, 8。

第一种变法,从第k个人开始数,即把循环的初始值设为k即可

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int main()
 6 {
 7     int n,m,k;
 8     bool vis[1000];
 9     int num=0,ans=0;
10     memset(vis,0,sizeof(vis));
11     scanf ("%d%d%d",&n,&m,&k);
12     for (int i= k;i <= n+1;i++)
13     {
14         if (i==n+1)
15             i=1;
16         if (vis[i]==0)
17             num++;
18         if(num==m)
19         {
20             ans++;
21             printf ("%d ",i);
22             vis[i]=1;
23             num=0;
24         }
25         if (ans==n)
26         break;
27     }
28     return 0;
29 }

 

编号为1,2,....,N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数).一开始任选一个正整数作为报数上限值M,从第一个人开始按顺时针方向自1开始顺序报数,报到M时停止报数.报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的下一个人开始 重新从1报数,如此下去,直至所有人全部出列为止.试设计一个程序求出出列顺序.

测试数据:
N=7;7个人 的密码依次为:3,1,7,2,4,8,4,首先M值为6(正确的出列顺序应为6,1,4,7,2,3,5)

变式2:仍从第一个人开始数,只是每一次m都要重新更新,我们就在每次数到当前m的同时,将m更新

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int main()
 6 {
 7     int n,m;
 8     int a[1000];
 9     bool vis[1000];
10     int num=0,ans=0;
11     memset(vis,0,sizeof(vis));
12     scanf ("%d%d",&n,&m);
13     for (int i = 1;i <= n;i++)
14     scanf ("%d",&a[i]);
15     for (int i= 1;i <= n+1;i++)
16     {
17         if (i==n+1)
18             i=1;
19         if (vis[i]==0)
20             num++;
21         if(num==m)
22         {
23             ans++;
24             printf ("%d ",i);
25             vis[i]=1;
26             num=0;
27             m=a[i];
28         }
29         if (ans==n)
30         break;
31     }
32     return 0;
33 }

 

n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。
输入格式 Input Format
一个正整数n,表示人的个数。输入数据保证数字n不超过100。
输出格式 Output Format
一个正整数。它表示经过“一二一”报数后最后剩下的人的编号。
样例输入 Sample Input :9
样例输出 Sample Output:3

 变式3:

怎么说呢,写的很复杂,首先它是1 2 1 2 1 2 1 2 报数的,题目描述的比较模糊。

先定义一个dir数组为1 2两个数字

两层循环,一个人对应一个数,如果这个人恰好在圈中而且报数为2,那么我们就可以把他踢出去

 1     int dir[3]={1,2};
 2     for (int i = 1;i <= n;)
 3      {
 4          for (int j = 0;j < 2;)
 5          {
 6              
 7              if(dir[j]==2&&vis[i]==0)
 8              {
 9                  num++;
10                  vis[i]=1;
11                  if (i==n)
12                      i=1;
13                  else 
14                      i++;
15                  j++;
16                  continue;
17              }
18          
19              if (vis[i]==1)
20              {
21                  if (i==n)
22                      i=1;
23                  else
24                      i++;
25                  
26                  continue;
27              }    
28             if (i==n)
29               i=1;
30             else 
31               i++;
32             if (j==1)
33               j=0;
34             else 
35               j++;

因为是一个环,所以我们可以清楚的了解到,每次如果i=n,我们就要重新从第一个人开始

如果这个人已经被访问过,我们就直接计算他的下一个人,但是应该报的数不变

 

完整代码....好长

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <cstring>
 5 using namespace std;
 6 int main()
 7 {
 8     int n;
 9     scanf ("%d",&n);
10     int num=0;
11     bool vis[1000];
12     memset(vis,0,sizeof(vis));
13     int dir[3]={1,2};
14     for (int i = 1;i <= n;)
15      {
16          for (int j = 0;j < 2;)
17          {
18              
19              if(dir[j]==2&&vis[i]==0)
20              {
21                  num++;
22                  vis[i]=1;
23                 if (i==n)
24                      i=1;
25                  else 
26                      i++;
27                  j++;
28                  continue;
29              }
30          
31              if (vis[i]==1)
32              {
33                  if (i==n)
34                      i=1;
35                  else
36                      i++;
37                  
38                  continue;
39              }    
40             if (i==n)
41               i=1;
42             else 
43               i++;
44             if (j==1)
45               j=0;
46             else 
47               j++;
48             if (num==n-1)    
49              {
50             for (int i = 1;i <= n;i++)
51              {
52              if (vis[i]==0)
53                cout<<i<<endl;
54              }
55              return 0;
56              }
57          }
58      }
59 
60     return 0;
61 }

 

 

围绕着山顶有10个洞,狐狸要吃兔子。

兔子说:“可以,但必须找到我,我就藏身于这十个洞中,你从10号洞出发,先到1号洞找,第二次隔1个 洞找,第三次隔2个洞找,以后如此类推,次数不限。”

 但狐狸从早到晚进 进出出了1000次,仍没有找到兔子。问兔子究竟藏在哪个洞里? 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<iostream>
 5 using namespace std;
 6 int b[10000+5];
 7 int main()
 8 {
 9     int m,n,z;
10         m=1;
11         n=1;
12         z=0;
13         memset(b,0,sizeof(b));
14         for(int i=1; i<=1000; i++)
15         {
16             if(i==1)
17             {
18                 b[i]=1;
19             }
20             else
21             {
22                 m++;
23                 if(n+m>10)
24                 {
25                     n=(n+m-1)%10+1;
26                     b[n]=1;
27                 }
28                 else
29                 {
30                     n=n+m;
31                     b[n]=1;
32                 }
33             }
34         }
35         for(int i=1; i<=10; i++)
36         {
37             if(b[i]==0)
38             {
39                 z++;
40                 printf("%d ",i);
41             }
42         }
43     return 0;
44 }

 

约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。

例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。
假定在圈子里前K个为好人,后K个为坏人,你的任务是确定这样的最少M,使得所有的坏人在第一个好人之前被杀掉。

 

约瑟夫环变形 先引入Joseph递推公式,设有n个人(0,…,n-1),数m,则第i轮出局的人为
f(i)=(f(i-1)+m-1)%(n-i+1);
f(0)=0;
f(i) 表示当前子序列中要退出的那个人(当前序列编号为0~(n-i));

拿个例子说:K=4,M=30;

f(0)=0;

f(1)=(f(0)+30-1)%8=5; 序列(0,1,2,3,4,5,6,7)中的5

f(2)=(f(1)+30-1)%7=6; 序列(0,1,2,3,4,6,7)中的7

f(3)=(f(2)+30-1)%6=5; 序列(0,1,2,3,4,6)中的6

f(4)=(f(3)+30-1)%5=4; 序列(0,1,2,3,4)中的4

……..

依据题意,前K个退出的人必定是后K个人,所以只要前k轮中只要有一次f(i)小于k则此m不符合题意。

 https://blog.csdn.net/u012717411/article/details/43916059 <--原网址

 1 #include <cmath>
 2 #include <climits>
 3 #include <iostream>
 4 using namespace std;
 5 typedef long long ll;
 6 const int INF=INT_MAX;
 7 const int maxn = 110;
 8 int k,ans[20],Joseph[20];
 9 void init()
10 {
11     for(int k=1;k<=14;k++){
12         int m=1,n=2*k;
13         while(1){
14           bool ok=true;
15           ans[0]=0;
16           for(int i=1;i<=k;i++){
17             ans[i]=(ans[i-1]+m-1)%(n+1-i);
18             if(ans[i]<k) {
19             ok=false;
20             break;
21             }
22           }
23           if(ok) break;
24           m++;
25         }
26         Joseph[k]=m;
27     }
28 }
29 int main()
30 {
31    init();
32    while(cin>>k&&k){
33      cout<<Joseph[k]<<endl;
34    }
35    return 0;
36 }
posted @ 2019-12-05 20:26  小又又  阅读(1150)  评论(0编辑  收藏  举报