翻硬币

翻硬币

  题目描述:

  一摞硬币共有m枚,每一枚都是正面朝上。取下最上面的一枚硬币,将它翻面后放回原处。然后取下最上面的2枚硬币,将他们一起翻面后再放回原处。再取3枚,取4枚……直至m枚。然后再从这摞硬币最上面的一枚开始,重复刚才的做法。这样一直做下去,直到这摞硬币中的每一枚又都是正面朝上为止。例如,m为1时,翻两次即可。m为2时,翻3次即可;m为3时,翻9次即可;m为4时,翻11次即可;m为5时,翻24次即可;…;m为30时,翻899次即可;…

  输 入:

  仅有的一个数字是这摞硬币的枚数m,0<m<1000。

  输 出:

  为了使这摞硬币中的每一枚又都是正面朝上所必需翻的次数。

  输入样例:

      30

  输出样例:

      899

  程 序:

    program Programl;
    var m:integer;
     function solve (m:integer):integer;
     vat i,t,d:integer;
       flag:boolean;
     begin
      if (m=1)then
       so1ve:= ①
      else begin
         d:=2*m+1;
         t:= 2;
         i:= 1;
         flag:=False;
         repeat
          if (t=1)then
           begin
            solve:= ②
            flag:=True;
           end
          else if ( ③ )then
              begin
               solve:=i*m-1;
               flag:=True;
              end
             else
              t:= ④ ;
          i:=i+1;
         until flag;
        end
      end;
    begin
     read (m);
     if ((m>0) and (m<1000)) then
      writeln ( ⑤ );
    end.

 

ans:

      ① 2
  ② i*m
  ③ t=2*m
  ④ (t*2)mod d
  ⑤ solve(m)

 

solve:

我们把每一次的翻转称为一次“小翻转”,从顶上开始连续翻n次称为
  一次“大翻转”。最后翻到全是正面朝上的状态时,如果用了 a 次大翻转和 b 次小翻转,那么总的翻转次数是 a*n + b。研究一次大翻转的置换结构。自底向上(为方便我们实际上用自左向右),用 1, 2, 3, ..., n 标记 n 个硬币,用 a' 表示硬币 a 的反面朝上状态。我们还在这一摞硬币的右端放一面镜子,那么,初始状态是:('|' 表示镜子的位置)
  [1 2 3 4 ... n-1 n | n' (n-1)' ... 4' 3' 2' 1']
  经过一次大翻转后,成为:
  [2 4 6 ... 5' 3' 1' | 1 3 5 ... 6' 4' 2']
  这个变换很有规律。只要令 a'=-a,可以看出,一次大翻转就是把编号
  为 c 的硬币变换到 c/2 (mod 2n+1) 的位置。看其逆变缓将更明显。
  显然,如果 2^m=1 (mod 2n+1),那么经过 m 次大翻转后,所有硬币都
  归到原位而且面朝上。此时共用了 m*n 次小翻转。
  显然,如果 2^m=-1 (mod 2n+1),那么经过 m 次大翻转后,所有硬币
  都将归到原位,并且正面朝下。如果不做最后那个大翻转的最后那个n
  个硬币翻过来的小翻转,那么就能得到正面全朝下的状态,此时需要
  m*n-1 次小翻转。剩下的问题是,证明只有上面所述两种情况下才会出现正面全朝上的局面。需要证明几个事实:
  1) 进行任意次大翻转后,再进行 0 < b < n-1 次小翻转,那么不可
  能出现全部硬币正面朝上的局面。
  (即要出现正面全朝上的情况,必然有 b=0 或 b=n-1.)
  2) 如果经过 m 次大翻转后全部硬币正面朝上,那么确实每个硬币都
  回到了原来的位置。
  3) 如果经过 m 次大翻转后全部硬币正面朝下,那么确实每个硬币都
  回到了原来的位置。
  2) 与 3) 比较容易证明,证法也类似。1) 证起来麻烦一些。当然可以用数学归纳法来证明。
  这个就是这个程序的思路,有了思路,我想你这个T和D应该就知道是什么意思了吧?

 

[问题描述]

         

一个关于翻硬币的问题,
一摞硬币 共有m枚,每一枚都是正面朝上。取下最上面的一枚硬币,将它翻面后放回原处。然后取下最上面的2枚硬币,将他们一起翻面后再放回原处。再取3枚,取4 枚……直至m枚。然后再从这摞硬币最上面的一枚开始,重复刚才的做法。这样一直做下去,直到这摞硬币中的每一枚又都是正面朝上为止。例如,m为1时,翻两 次即可。m为2时,翻3次即可;m为3时,翻9次即可;m为4时,翻11次即可;m为5时,翻24次即可;…;m为30时,翻899次即可;…
输 入:
  仅有的一个数字是这摞硬币的枚数m,0<m<1000。
  输 出:
  为了使这摞硬币中的每一枚又都是正面朝上所必需翻的次数。
  某单元输入:
      30
  某单元格等于:
      899
 
下面是我自己的两种解法
[解法一]
思路:每一轮的翻转后,能得到每一枚硬币当前的位置的位置,如果硬币重新回到原先的位置(基数个硬币时)或者相反的位置(偶数个硬币时),就的到了求解。
        //查找每轮循环后的位置
        int GetTurnAfterPos(int n, int turn, int m)
        {
               int TurnNum ;//每轮翻转次数
               int TurnAfterPos; //每轮循环后的位置
              TurnNum = turn - n + 1;
              if(TurnNum % 2 == 0) //为偶数次循环
                     TurnAfterPos = n + TurnNum / 2;
              else                //为奇数次循环
                     TurnAfterPos = (TurnNum + 1) / 2;
              return TurnAfterPos;
        }
        //每一轮翻转之后的位置:
        int solve(int turn, int * CurrentPos, int m)
        {
             if(turn == 0)
                    return 0;
             for(int i = 0; i < turn; i ++)
             {
                    int RetNum = GetTurnAfterPos(i+1, turn, m);
                    CurrentPos[RetNum-1] = CurrentPos[m+i];
             }
             for(i = 0; i < m; i ++)
            {
                   CurrentPos[i+m] = CurrentPos[i];
             }
             return 0;
       }
       int main()
       {
              int m = 30;//硬币总数 
              int * CurrentPos = new int[m*2]; 
              int n = 0;//翻转次数
              bool flag = false;
              do
             {  
                   n++;
                   for(int i = 0; i < m*2; i++)
                           CurrentPos[i] = i % m + 1;
                  int x = n / m;
                  int y = n % m;
                  for(i = 0; i < x; i++)
                          solve(m, CurrentPos, m);
  
                  solve(y, CurrentPos, m);
                  for(i = 0; i < m; i ++)
                  {
                           printf("%d ",CurrentPos[i]); 
                  }
                  //solve(5, CurrentPos, m);
                  printf("/n");
                  for(i = 0; i < m; i++)
                 {
                           if(CurrentPos[i] != i+1)
                           {
                                 break;
                           }  
                           if(i == m - 1 && n > 1)
                                 flag = true;
                }  
          }
          while(!flag);
               if(m % 2 == 0)
                      printf("翻转的总次数是:    %d/n", n - 1); 
              else
                     printf("翻转的总次数是:    %d/n", n); 
              delete []CurrentPos;
              return 0;
     }
[解法二]
思路:不管硬币的次序,只是记住每次的翻转和交换,硬币的初始状态都是TRUE,每个COIN经过多伦的翻转后都变回TRUE,那么就求出了解,这是最简单的思路。
     void solve(int m)
    {
          bool * CurrentSurface = new bool[m];
          for(int i = 0; i < m; i++)
          CurrentSurface[i] = true;
         int turnTimes = 0;//翻转次数
         bool bSuccess = false;
         do
         {
              for(int i = 0; i < m; i++)
              {
                     for(int j = 0; j < (i+1) / 2; j ++)
                    {
                             //turnCoin(j, i - j + 1);
                             bool temp = CurrentSurface[j];
                             CurrentSurface[j] = !CurrentSurface[i - j];
                             CurrentSurface[i - j] = !temp;
                    }
                    if((i+1) % 2 == 1)
                             CurrentSurface[i/2] = !CurrentSurface[i/2];  
                   turnTimes=turnTimes+1;
                   bSuccess = true;
                   for(int n = 0; n < m; n ++)
                   {
                        if (CurrentSurface[n] == false)
                        {
                             bSuccess = false;
                             break;
                        }
                   }
                   if(bSuccess)
                        break;
              }
         }while(!bSuccess);
         printf("翻转的总次数是:    %d/n", turnTimes);
         delete [] CurrentSurface;
}
 
int main()
{
     int CoinCount = 1;
     while(CoinCount > 0)
     {
          printf("请输入硬币的总数:");
          scanf("%d",&CoinCount);
          solve(CoinCount);
     }      
      return 0;
}
 
网上还有几种解法,也同时列了出来:
[解法三]
这个是最难理解的一个算法,因为没有注释,呵呵。
 
int solve(int  m); 
int main() 
{  
     int  m;  
     do  {  
              scanf("%d",  &m);  
              if(m  >  0  &&  m  <  1000)  
              printf("%d/n/n",  solve(m));  
     }  while(m  >  0  &&  m  <  1000); 

int  solve(int  m) 
{  
     int  I,  
     t, 
     d, 
     s = -  1; //翻转的次数 
    int  flag;
    //如果只有一枚硬币,翻两次就能达到目标
    if(m == 1) 
         s = 2; 
    else
   { 
         d = 2  *  m  +  1;    //确定硬币是经过偶数次翻转还是奇数次翻转
         t = 2;        //表示一个COIN必须翻转偶数次,才能从正面继续翻回到正面。 
         I = 1;        //翻转的轮数,每轮为从1翻转到m
         flag = 0;    //退出循环标志,翻转完成标志
  
         printf("d  =  %d:/n",  d);  
         do { 
              printf("/tt(%d)  =  %d,  /t  s:  %3d,  %3d/n",  I,  t,  I  *  m,  I  *  m  -  1); 
              if(t == 1)            //
              {  
                       s = I * m; 
                       flag = 1; 
              } 
              else if(t == 2 * m)      { 
                      s = I * m - 1; 
                      flag = 1; 
             } 
             else                      
             {
                     t = (t  *  2)  %  d; 
             }
             I = I + 1; 
       }  while(!flag); 
   } 
   printf("s  =  %d,  I  =  %d,  t  =  %d,  d  =  %d/n",  s,  I,  t,  d); 
   return  s; 
}
 
[解法四]
 思路: 设 题 设的从1到n的翻转为一轮番转,位置   pos,   turnNum   为位置pos所需要的翻转次数 ,假设有5个硬币,我们按其位置分别编号   1,2,3,4,5 经过一轮翻转后,变成了5,3,1,2,4;可以看到原来1位置的硬币现在到了3。这一点说明一轮翻转过后,pos的排列是有规律 的,另外,任意位置上的元素的翻转次数为turnNum   =     num+1-pos;   
由 以上分析得到了,任意位置pos,经过一轮番转后的位置变为了new_pos   =   num/2   +   1   +   turnNum/2   -   turnNum%2*turnNum   所以,可以由上式计算出new_pos。那么,如果new_pos也为1的话,则所有的硬币都回到了原来的位 置,且都朝正面。   
  #include   "stdio.h"  
  int   solve(int   m)  
  {  
          int   I   =   0;  
          int   pos,turnNum,temp,s;  
          turnNum   =   0;  
          if(m   ==   1)   s   =   2;  
          else  
          {  
                  pos   =   1;  
                  I   =   0;  
                  do  
                  {  
                          I++;  
                          temp   =   m+1-pos;  
                          turnNum   +=   temp;  
                          pos   =   m/2   +   1   +   pos/2   -   temp%2*(pos/2)*2;  
                  }while(pos!=1);  
                  s   =   I*m   -   turnNum%2;         //the   sum   of   times  
          }  
          return   s;  
  }  
   
  main()  
  {  
          int   m;  
          do  
          {       scanf("%d",&m);  
                  printf("the   sum   is   :   %d/n",solve(m));  
          }while(m   !=   -1);  
  }   
  
[解法五]
      思路: 直接模拟
      #include<iostream>  
      #include<stdlib.h>  
      #define   swap(a,b){int   t=!a;   a=!b;   b   =   t;}   
      int     n;  
      int   flag[1000];  
       
       
      bool   isright()  
      {            
                for(int   i=0;   i<n;   i++)    
                        if(!flag[i])  
                            return   false;  
                return   true;              
      }    
     
      int   turncoin()  
      {          
              int   total   =   1;  
                       
              while(1)  
              {  
                    int   k   =   total%n;  
                    int   i,j;  
                    for(   i=0,j=k;   i<=j;   i++,   j--)  
                          swap(flag[i],flag[j]);  
                    total++;                  
                    if(isright())  
                          return   total;        
              }  
              return   total;  
      }  
       
      void   init()  
      {          
     
                  for(int   i=0;   i<n;   i++)    
                        flag[i]   =   1;    
                flag[0]   =   0;  
     
      }  
      int   main()  
      {  
               
    while(std::cin >> n && n>0)  
              {          
                    init();    
     std::cout << turncoin() << std::endl;              
              }  
              return   0;  
                             
      }    
 
 
思路:

设硬币从最上一枚到最底下一枚编号为1到n,且从1到n的m次翻转为一轮,则经过一轮翻转后原来在i位置的硬
币Ci翻转到新位置为f(i), 在这一轮翻转中Ci换面次数为g(i),容易得到:
      f(i) = (m-i)/2+1, 当m-i为偶数; (m+i+1)/2, 当m-i为奇数;
      g(i) = m+1-i
      考虑一轮翻转对应的置换H(m)(循环形式):
      例如H(4) = (1,3,4)(2)
      H(5) = (1,3,2,4,5)
      H(10) = (1,6,3,7,9,10)(2,5,8)(4)
      置换的阶等于其各个循环的阶的最小公倍数。对任意置换,若阶为k,则它的k次幂为单位置换。
有可以证明的几个重要结论:
      <1>:设置换H(m)的阶为k,则经过k轮翻转后,所有硬币重新回到起始位置,且正反面一致。若全部为反面,则
           由翻转的可逆性知前一次翻转的状态恰好为整摞硬币的逆序且全为正面。
      <2>:置换H(m)的阶为1所在的循环的阶。
 

/*
* poj1662 flip Coins 翻硬币问题
* happynp 12.21.2008
*/

#include<iostream>
using namespace std;

__int64 flip(int m)
{
      int p = 1; // 跟踪最上面的硬币设其位置是1
      int k=0;   // 翻转轮数
      int c=0;   // 累计换面次数
      do {
            k++; // 求1所在的循环的阶
            c += (m+1-p)&0x1;
            // 翻转一轮后硬币的位置
            if(((m-p)&0x1)==0) // m-p为偶数
                  p = ((m-p)>>1)+1;
            else
                  p = (m+p+1)>>1;           
      }while(p!=1);
      __int64 ret = k;
      ret = ret*m-(c&0x1);
      return ret;
}

int main()
{
      int t, m;
      scanf("%d", &t);
      while(t-->0) {
            scanf("%d", &m);
            if(m==1) printf("%d\n", 2);
            else printf("%I64d\n", flip(m));
      }
      return 0;
}

posted @ 2012-09-27 14:37  Mose  阅读(668)  评论(0编辑  收藏  举报