Fork me on GitHub

程设刷题 | 洗纸牌问题

目录

纸牌

题目描述

分析

方法一(极有可能超时)

方法二(简单)

另述


纸牌

题目描述

有2N张纸牌,标号从1到2N。一次洗牌,可以将牌的排列改成n+1,1,n+2,2,...,n,2n。请问这样洗牌几次,可以让牌回到原始的状态。比如N=2时,1234->3142->4321->2413->1234 一共洗4次。

输入

每行输入一个整数N(1≤N≤10,000),N如果为0,表示输入结束,不需要处理。

输出

每行输出一个样例的结果。

样例输入

1
2
3
4
5
6
0

样例输出

2
4
3
6
10
12
 


分析

题目给的N达到了100,000,一般的方法极有可能超时。

下面是最先想到的成功超时的方法:

 

方法一(极有可能超时)

#include <iostream>//1210 Eddy's 洗牌问题 模拟  
#include <string>  
using namespace std;  
  
int n,m;  
void change(string &str )  
{  
    string a;  
    for(int i=1;i<=n;i++)  
    {  
        cc++;  
        a=str[n+i];  
        str.erase(n+i,1);  
        str.insert(i*2-1,a);  
    }  
}  
  
int main()  
{  
    string str,tmpstr;  
    while(cin>>n)  
    {  
        str.resize(n*2+1);  
        for(int i=1;i<=n*2;i++)  
            str[i]=i+'0';  
        m=0;  
        tmpstr=str;  
        while(1)  
        {  
            change( str );  
            m++;  
            if(tmpstr==str) break;  
        }  
        cout<<m<<endl;  
    }  
    return 0;  
}

后经过一番思考和演算……我发现这道题是有规律的。

规律就是1的位置,很容易发现1的位置是从1->2->4->8……如果超过数尾则从头偏移,总之只要经过若干次移动,1再次移动在1的位置,就能够保证洗牌洗回了原序列。

 

方法二(简单)

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    while(scanf("%d",&n),n)
    {
        int cnt=0;
        int pos=1;
        do
        {
            if(pos<=n) pos*=2;//记录1的位置:1-2-4-8-...直到01串尾
            else pos=(pos*2-1)%(2*n);//相当于环形存储(求余操作)
            cnt++;
        }
        while(pos!=1);
        printf("%d\n",cnt);
    }
    return 0;
}

 

另述

当然方法不止这些!比如这种,只追踪第一张牌的位置,只有当他在第n+1的位置时下一次才会回到初始位置.你并没有考虑其他牌的位置,因为他们都是相关的,位置不会乱.

#include <stdio.h>
int main()
{
    int n, x, c;
    for(; scanf("%d", &n) != EOF; printf("%d\n", c))
    for(c = x = 1; (x = x>n ? (x-n)*2-1 : x*2) - 1; c++);
    return 0;
}

 

 

 

【附:一文一图】

 

 

 

posted @ 2017-08-29 21:45  zhouie  阅读(352)  评论(0编辑  收藏  举报