pku1781,pku1012,pku2244(约瑟夫环问题)

假设当前剩下i个人(i<=n),显然这一轮m要挂(因为总是从1开始数).经过这一轮,剩下的人是:1 2 3 ... m- 1 m + 1 ... i, 我们将从m+1开始的数映射成1, 则m+2对应2, n对应i - m, 1对应成i - m + 1  m - 1对应i - 1,那么现在的问题变成了已知i - 1个人进行循环报数m,求出去的人的序号。假设已经求出了i- 1个人循环报数下最后一个出去的人的序号X0,那么它在n个人中的序号X1=(X0+ m - 1) % n + 1,  最初的X0=1 ,反复迭代X0和X1可以求出.

#include <stdio.h > main() { int n, m,i, s=0; printf( "N M = "); scanf("%d%d ",&n,&m); for(i=2;i<=n;i++)s=(s+m)%i; printf("The winner is %d/n ", s+1); }  

http://162.105.81.212/JudgeOnline/problem?id=1781

题意:有n个人,按1~n排列成一个圈,每隔一个就剔除一人,知道剩下最后一个人为止。

列出少数的规律为:

//当n分别为: (1) (2 3) (4 5 6 7) (8 9 10 ...结果对应下面的

//构造出数列:(1) (1 3) (1 3 5 7) (1 3 5 7 9 1...规律

//每一组元素个数为:1 2 4  8...

按照规律些的代码:

#include<iostream> #include<string> #include<cmath> using namespace std; int f[50]; char ch[10]; int main() { int x, y, i, j; int max; while(scanf("%s",ch)) { if(strcmp(ch, "00e0")==0) break; x = (ch[0]-'0')*10 + (ch[1]-'0'); y = ch[3] - '0'; max = pow(10.0, y) * x; f[0] = 1; j = 1; //当n分别为: (1) (2 3) (4 5 6 7) (8 9 10 ...结果对应下面的 for(i=1; i<=28; i++) //构造出数列:(1) (1 3) (1 3 5 7) (1 3 5 7 9 1...规律 { //每一组元素个数为:1 2 4 8... j *= 2; //j<<=1; f[i] = f[i-1] + j; } for(i=0; i<28; i++) //这里还不是很理解!! { if(f[i]<max && max<=f[i+1]) { max -= f[i]; break; } } //for(i=0; i<=28; i++) // printf("%d ",f[i]); //printf("/n%d",max); j=1; for(i=2; i<=max; i++) j += 2; printf("%d/n",j); } return 0; }  

网上找的资料还有别的解法:

设剩下人数为n,
若n是偶数,则一轮过后只剩下奇数位的人,有n = n / 2,原本在奇数位的数字变成(k+1) / 2;
若n是奇数,则一轮过后只剩下奇数位的人,特别的原本为第一位的也应被删除,原本第3位的变成第一位,于是有n = (n-1) / 2,原本在奇数位的数字变成(k-1) / 2;

经过有限次数后,n一定变成1,这就是最后的luck。
因此逆推上去就知道luck开始所处位置了。
递归程序:
int luck(int n)
{
      if(n == 1)
        return 1;
      if(n&1)
        return luck( (n-1)/2 ) * 2 + 1;
      else
        return luck( n/2 ) * 2 - 1;
}
因为每次递归n都减半,复杂度是O(log(n))

在具体数学上面的解法为:

 

竟然在chapter1.3就有这个问题的分析,而且还进一步指出了一个更简单的解法:
b1b2……bk是n的二进制表示,则
luck( (b1b2……bk)2 ) = (b2……bkb1),即把n循环左移一位!

新的程序
int luck(int n)
{
      int i = 1;
      while(i <= n) // 该循环用于产生一个i = 2^(k+1),k是n的二进制位数
        i<<=1;
      return ( (n - (i>>1) )<<1) + 1;
}

 

pku1012

http://162.105.81.212/JudgeOnline/problem?id=1012

题意:输入k,表明有k个好人和k个坏人同时围成一个圈,前面k个人是好人,后面k个人是坏人;求全部把坏人剔除出局的整数m。

分析:因为数据很大,直接模拟肯定超时,但是要加上一个小小的记忆优化就可以了。

#include<iostream> using namespace std; int res[15]; int main() { int i, num, k, ans, len; memset(res, 0, sizeof(res)); while(scanf("%d",&k) && k) { ans = i = 0; if(res[k]) //加了一个小小的记忆优化219ms过了。 { printf("%d/n",res[k]); continue;} else { while(1) { i++; len = k * 2; num = 0; while(1) { num = (num + i - 1) % len; //模拟数到第i个人被剔除,当前的循环位置是num; if(num >= k) //如果该位置是坏人,则剔除; len--; else break; //如果是处在好人的位置,则退出,i++; } if(len == k) //说明坏人已经全部出局; { res[k] = i; break;} //用记忆数组保存起来,备用; } } printf("%d/n",i); } return 0; } 

pku2244:

题意:1-n个城市要断网,数m个断一次,问m应该数几才能让2号城市最后断网,刚开始1号城市会断。。不管m是几。。所以转换后题意为:有n-1个城市,问m数几才能让1号城市最后断网。。和上一题差不多,改下代码就好了。

分析://这题我们这么理解由于的哥城市第一个出局,那么下一轮是从城市2开始的

//对接下来n-1个城市编号,城市2就是0,然后依次下去
//这样就转化成了n-1个人的约瑟夫问题

 

#include<iostream> using namespace std; int res[155]; int main() { int i, num, n, ans, len; memset(res, 0, sizeof(res)); while(scanf("%d",&n) && n) { i = 1; if(res[n]) //加了一个小小的记忆优化。还是16ms; { printf("%d/n",res[n]); continue;} else { for(i=2; ; i++) { //i++; len = n - 1; num = 0; while(1) { num = (num + i - 1) % len; //模拟数到第i个人被剔除,当前的循环位置是num; if(num == 0) //如果下标是0,就是1号出局时。 break; len--; //如果是出局一人; } if(len == 1) //说明1号是最后才出局的; { res[n] = i; break; } //用记忆数组保存起来,备用; } } printf("%d/n",i); } return 0; } 

牛人的更简单的代码:

#include <stdlib.h> #include <stdio.h> int main(int argc, char** argv) { int n,i,j,s; while (scanf("%d",&n)!=EOF) { if (n==0) break; for (i=2;;i++) { s=0; for (j=2;j<=n-1;j++) s=(s+i)%j; if (s==0) { printf("%d/n",i); break; } } } return (EXIT_SUCCESS); } 

 

 

你可能感兴趣的:(优化)