poj 2886 Who Gets the Most Candies?


N 个小孩围成一圈,他们被顺时针编号为 1 到 N。每个小孩手中有一个卡片,上面有一个非 0 的数字,游戏从第 K 个小孩开始,他告诉其他小孩他卡片上的数字并离开这个圈,他卡片上的数字 A 表明了下一个离开的小孩,如果 A 是大于 0 的,则下个离开的是左手边第 A 个,如果是小于 0 的,则是右手边的第 -A 个小孩。游戏将直到所有小孩都离开,在游戏中,第 p 个离开的小孩将得到 F(p) 个糖果,F(p) 是 p 的约数的个数,问谁将得到最多的糖果。输出最幸运的小孩的名字和他可以得到的糖果。

//以上的翻译是从网上找的

首先呢,第P个小孩将得到P的约数个糖果,这就要引入一个数学名词,反素数,反素数的性质就是,比他小的数的约数个数都没他多,那么我们只要在1到n的范围内找到最大的反素数就能知道最多的糖果数,这个其实用反证法就能证明,假设p是1到n内的最大反素数,那么,如果在p到n内还有数的约数比p多,那么显然p就不能算是最大的反素数了,所以我们就先把50万以内的反素数提前打表,他们的约数也提前打好表,这样的话,就不用所有人都出来后才得出最终结果了。

假设p是1到n内最大的反素数,我们只需模拟p次这个出队过程了,然后有一个需要注意的地方是,下一个人的位置怎么求,这里面有一种是相对位置,还有一种是最初位置,很显然,由于数据量太大,不能通过简单的模拟来实现最初位置和相对位置的转化,这里面,能直接求出来的是相对位置,假设一个人的位置是k,手上卡片的数字是+m,那么下一个人的位置应该是p+m,但是由于他的出队,这个相对位置就要减1了,是p+m-1,但是如果一个人的卡片是-m,此时他的退出就对下一个人的相对位置没有影响了,因为下一个人的位置应该是p-m,在这个人之前,就没有影响了。然后需要注意的是可能会出现负数和0,这就要适当的处理一下了。

然后就是相对位置与最初位置的转换,这里用到了线段树,线段树所附加的信息是该区间还有多少人,每踢掉一个人,区间值变化一次,然后就相当于一个动态的队列了,中间踢掉某个人,整个序列的长度减1,每个人的位置发生了变化,然后再踢相对位置为p的人,只要从头数到第p个人,踢掉就行了。线段树的话,我们先从叶子节点看,踢掉一个人,就相当于这个叶子节点的值就变成0了,然后从1到n,数叶子节点为1的个数,到p的时候把踢掉该位置的人就行了,这样就好理解点了,然后根据点更新到段就能实现快速的插入了。


//以上是摘自http://blog.csdn.net/sdj222555/article/details/


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

using namespace std;
#define maxn 500005
#define ls o<<1
#define rs o<<1|1
#define MID (l+r)>>1

const int antiprime[] = {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,554400};
const int factor[] = {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,216};
struct line_tree{
    int l,r;
    int cnt,value;
    char name[10];
}tree[maxn<<3];
inline void push_up(int o)
{
    tree[o].cnt = tree[ls].cnt + tree[rs].cnt;
}
void build(int l,int r,int o=1)
{
    tree[o].l = l;
    tree[o].r = r;
    if(l==r){
        tree[o].cnt = 1;
        scanf("%s %d",tree[o].name,&tree[o].value);
        return;
    }
    int mid = MID;
    build(l,mid,ls);
    build(mid+1,r,rs);
    push_up(o);
}

line_tree update(int x,int o=1)
{
    tree[o].cnt--;
    if(tree[o].l==tree[o].r){
        return tree[o];
    }
    if(x<=tree[ls].cnt)return update(x,ls);
    else return update(x-tree[ls].cnt,rs);
    //push_up(o);
}

int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)==2){
        build(1,n);
        int best;
        for(int i=0;antiprime[i]<=n;i++){
            best=i;
        }
        line_tree te;te.value=0;
        for(int i=1;i<=antiprime[best];i++){
            if(te.value>0){
                k = ((k + te.value- 2) % tree[1].cnt + tree[1].cnt) % tree[1].cnt + 1;
                //由于要从下一个人开始数,并且k要减一,所以减2,其实是 k-1+te.value-1
            }
            else{
                k = ((k + te.value - 1) % tree[1].cnt + tree[1].cnt) % tree[1].cnt + 1;
            }
            te = update(k);
        }
        printf("%s %d\n",te.name,factor[best]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值