POJ 2886 Who Gets the Most Candies?

题意:

  N个孩子顺时针坐成一个圆圈且从1到N编号,每个孩子手中有一张标有非零整数的卡片。第K个孩子先出圈,如果他手中卡片上的数字A大于零,下一个出圈的是他左手边第A个孩子。否则,下一个出圈的是他右手边第(-A)个孩子。第p个出圈的孩子会得到F(p)个糖果,F(p)为p的因子数。求得到糖果数最多的是哪个孩子及得到多少糖果。

 

分析:

  出圈过程有点类似约瑟夫环。假设当前出圈的是剩余孩子中的第K个,他手中的数字为A。若A大于零,下一个出圈的就应该是剩余孩子中的第(K-1+A-1)%n+1个;若A小于零,下一个出圈的就应该是剩余孩子中的第((K-1+A)%n+n)%n+1个。问题的关键是如何求得出圈孩子的原始位置,这里我用的是线段树。线段树的每个节点的sum存储了所在区间还有多少孩子留下,查询节点rt中第num个孩子的原始位置时,如果num<=st[rt<<1].sum,则在左孩子节点中查询第num个孩子的原始位置;否则在右孩子节点中查询第num-st[rt<<1].sum个孩子的原始位置。

  另外,本题还用到了反素数。反素数的定义:对于任何正整数x,其约数的个数记做g(x)。例如g(1)=1,g(6)=4。如果某个正整数x满足:对于任意i(0<i<x),都有g(i)<g(x),则称x为反素数。对于本题,设p为不大于N的反素数,则第p个出圈的孩子得到的糖果最多,为p的约数个数。

 

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>

using namespace std;

const int maxn = 500100;
char name[maxn][11];
int val[maxn];
/* a为反素数表,b为对应的约数个数 */
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};

struct Node
{
    int l, r, sum;
}st[maxn<<2];

void build(int l, int r, int rt)
{
    st[rt].l = l;
    st[rt].r = r;
    st[rt].sum = r - l + 1;
    if (l == r) return ;
    
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
}

/* 求当前留下的孩子中第num个孩子的原始位置 */
int query(int num, int rt)
{
    st[rt].sum--;
    if (st[rt].l == st[rt].r)
    {
        return st[rt].l; 
    }
    if (num <= st[rt<<1].sum)
        return query(num, rt << 1);
    return query(num - st[rt<<1].sum, rt << 1 | 1);
}

int main()
{
    int n, k;
    while (scanf("%d %d", &n, &k) != EOF)
    {
        int i = 0, Max = 0, p = 0; /* 第p个出去的孩子得到最多的糖果,为Max */ 
        while (a[i] <= n)
            i++;
        p = a[i-1];
        Max = b[i-1]; 
        build(1, n, 1);
        for (i = 1; i <= n; ++i)
        {
            scanf("%s %d", &name[i], &val[i]);
        }
        
        int idx; /* idx为当前出去的孩子的原始位置 */ 
        for (i = 0; i < p; ++i) /* 只要求出第p个出去的孩子即可 */
        {
            n--;
            idx = query(k, 1);
            if (n == 0) break;
            if (val[idx] > 0)
                k = (k-1+val[idx]-1)%n+1;
            else
                k = ((k-1+val[idx])%n+n)%n+1; 
        }
        printf("%s %d\n", name[idx], Max);
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值