POJ 2886 Who Gets the Most Candies? (约瑟夫环+反素数+线段树)

Who Gets the Most Candies?
Time Limit: 5000MSMemory Limit: 131072K
Total Submissions: 7353Accepted: 2170
Case Time Limit: 2000MS

Description

N children are sitting in a circle to play a game.

The children are numbered from 1 toN in clockwise order. Each of them has a card with a non-zero integer on it in his/her hand. The game starts from theK-th child, who tells all the others the integer on his card and jumps out of the circle. The integer on his card tells the next child to jump out. LetA denote the integer. IfA is positive, the next child will be theA-th child to the left. IfA is negative, the next child will be the (A)-th child to the right.

The game lasts until all children have jumped out of the circle. During the game, thep-th child jumping out will getF(p) candies whereF(p) is the number of positive integers that perfectly dividep. Who gets the most candies?

Input

There are several test cases in the input. Each test case starts with two integersN (0 <N≤ 500,000) andK (1 ≤ KN) on the first line. The nextN lines contains the names of the children (consisting of at most 10 letters) and the integers (non-zero with magnitudes within 108) on their cards in increasing order of the children’s numbers, a name and an integer separated by a single space in a line with no leading or trailing spaces.

Output

Output one line for each test case containing the name of the luckiest child and the number of candies he/she gets. If ties occur, always choose the child who jumps out of the circle first.

Sample Input

4 2
Tom 2
Jack 4
Mary -1
Sam 1

Sample Output

Sam 3
 
约瑟夫环:
一般的约瑟夫环问题是,有n个人围成一圈,从第一个人开始数,数到m的人出列,问最后一个出列的是哪个人。可以通过递推来求解,有两种形式:
f[1] = 0,  f[n] = (f[n-1] + m)%n; 最后一个出列的人是:f[n] + 1;
f[1] = 1,  f[n] = (f[n-1] + m)%n; if(f[n] == 0)f[n] = n; 最后一个出列的人是:f[n];
值得注意的是,中间过程求得的f[i](1<i<n),得到的结果并不是正确的,而最终结果f[n]却是正确的。
反素数:
定义整数n的约数个数为g[n],若对于1<=i<n,都有g[i]<g[n],则称n为反素数。
 
对于这道题,N个人轮流出列,然后求其中最大的F[p](1<=p<=N),很显然求的就是N以内的最大反素数的约数个数。所以只需打表把500000以内的所有反素数以及他们对应的约数个数打出来。
还要求解是哪一个人最后出列,这就要求求解约瑟夫环了。然而上面的公式已经不再适用,但可以模拟的方法,推出类似的公式。
假如把n个人编号为0~n-1,那么第一个出列的人就是K-1,他是第K个人,假定初始idx = K,此时转向要根据A[idx]来定,如果[idx]>0,注意到此时K-1已经出列了,而当前是从K开始数了,但K编号已经变成了K-1,所以有K = (K-1+A[idx)%num;既新得到的K是重新编号后下一个要出列的人,如果A[idx]<0,因为往回走的编号都是小于K-1的,它们的编号都不变,所以有K = ((K + A[idx])%num+num)%num,其中+num是为了防止出现负数。由于每次求出来的K都不是原来的编号了,这里就用到线段树去查询,查询哪个点的位置还足够让他成为第K个人。
 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
#define SIZE 500005
#define ls l,mid,rt<<1
#define rs mid+1,r,rt<<1|1

using namespace std;

int N,K;
int A[SIZE];
int sum[SIZE<<2];
char nam[SIZE][15];

int a[37]={1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,1260,1680,
           2520,5040,7560,10080,15120,20160,25200,27720,45360,50400,
           55440,83160,110880,166320,221760,277200,332640,498960,500001};

int b[37]={1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,64,72,
           80,84,90,96,100,108,120,128,144,160,168,180,192,200,1314521};

void build(int l,int r,int rt)
{
    sum[rt] = r - l + 1;
    if(l == r)return;
    int mid = (l + r) >> 1;
    build(ls);
    build(rs);
}
int update(int l,int r,int rt,int p)
{
    sum[rt] --;
    if(l == r)
        return l;
    int mid = (l + r) >> 1;
    if(sum[rt<<1] >= p) return update(ls,p);
    else return update(rs,p-sum[rt<<1]);
}

int main()
{
    while(~scanf("%d%d",&N,&K))
    {
        for(int i=1; i<=N; i++)
            scanf("%s%d",nam[i],&A[i]);
        int t = 0;
        while(a[t] <= N)
            t ++;
        int p = a[t-1];
        int ans = b[t-1];
        build(1,N,1);
        int ct = 0,idx;
        K --;
        int all = N;
        while(ct < p)
        {
            all --;
            idx = update(1,N,1,K+1);
            if(ct == p-1)
                break;
            if(A[idx] > 0)
                K = (K + A[idx] - 1) % all;
            else
                K = ((K + A[idx]) % all + all) % all ;
            ct ++;
        }
        printf("%s %d\n",nam[idx],ans);
    }
    return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值