Double Dealing----HDU4259----找规律

题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=4259

/*
Author:Bob Lee
2012.8.27
===================================
首先是题目意思的理解问题
有n张牌,然后一次给k个人,多的话就循环着给
然后依次将每个人手中的牌收回来,第一个的放在最上面,并且是倒着的
例如1手中的是1,4,7,10
那么收回去放着的时候就是10,7,4,1
一次往后放,然后再发,再收回来,问你要多少次的循环才能恢复到初始的情况
这个就是题意。
以样例10,3来说
第一次之后为10,7,4,1,8,5,2,9,6,3
第二次之后为3,2,1,10,9,8,7,6,5,4
第三次之后为4,7,10,3,6,9,2,5,8,1
第四次之后为1,2,3,4,5,6,7,8,9,10

我们可以发现一个规律,1它的位置变化是1-4-3-10-1
而4的位置变化为4-3-10-1-4
3的位置变化为3-10-1-4-3
10的位置变化为10-1-4-3-10
不难得出结论这一个循环上的数字的位置变化是一样的,也就是他们要回到自己的初始位置的步数是一样的
那么我们的任务就变成来找出每个这种链的步数,然后求出他们的最小公倍数,这样就得到了结果
这个例子中其它的就是2-7-2和5-6-9-8-5
这个规律我们可以在一次模拟之后就知道了,那么我们首先要做的就是模拟一次,然后通过递推a[data[i]] = i的到我们要得循环链
因为1下一个是4,4后面是10,10后面是3,这样是不是就出来了?
还有一个我们可以发现当n<=k的时候,每个人手中是只有一张牌的,那么收回的时候根本就不会影响到顺序,也就是只要1次就够了,这个就可以加进去优化一下

*/


#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

#define MAXN 1000


long long a[MAXN];
long long data[MAXN];
struct player
{
    int count;
    int num[MAXN];
}p[MAXN];
bool visit[MAXN];
int n,k;

void init()
{
    for(int i=1;i<=n;i++)
    {
        p[i].count = 0;
        visit[i] = false;
        data[i] = i;
    }

}

void give()//模拟给牌的过程
{
    int i;
    for(i=1;i<=n;i++)
    {
        int t = i%k;
        if(t==0)
            p[k].num[++p[k].count] = data[i];
        else
            p[t].num[++p[t].count] = data[i];
    }
}

void back()//模拟收牌的过程
{
    int i,j;
    int q = 1;
    for(i=1;i<=k;i++)
    {
        for(j = p[i].count ; j>=1 ;j--)
        {
            data[q++] = p[i].num[j];
        }
    }
    //由于已经模拟来一次过程,我们就可以找出规律了
    for(i=1;i<=n;i++)
    {
        a[data[i]] = i;
    }
}

long long gcd(long long a,long long b)//求最小公约数
{
    return a ? gcd(b%a, a) : b;
}

int main()
{
    while(scanf("%d%d",&n,&k) && n+k)
    {
        if(n<=k)//那个优化的过程
        {
            cout<<1<<endl;
            continue;
        }
        init();
        give();
        back();
        long long ans = 1;
        for(int i=1;i<=n;i++)
        {
            if(visit[i])//如果这个循环节已经访问过来,则跳过
                continue;
            long long cnt = 0;
            int key = i;
            while(!visit[key]){
                visit[key] = true;
                cnt++;
                key = a[key];
            }

            //求出它们的最小公倍数
            ans = ans/gcd(ans,cnt)*cnt;
        }
        cout<<ans<<endl;
    }
    return 0;
}











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值