poj2886 Who Gets the Most Candies?(单点更新+约瑟夫环+素数打表)


http://poj.org/problem?id=2886


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


思路:我觉得这题难就难在如何在一开始把线段树和约瑟夫环联系起来,就是这点困扰了我许久。题中模型是一个环,下一人的退出序号是由前异人卡片上的数字决定的,这里有两个难点。第一,普通约瑟夫环求的是最后一人出场序号,这个是求约数最多的出场序号,必须先打素数表来确定递归终止条件;第二普通约瑟夫环每个人之间的递推间隔是固定的,而这个递推间隔由卡片决定,而要想知道卡片必须知道那个人的实际位置。而我们知道约瑟夫环在递推过程中下一步即为上一步的相对位置,而这个相对位置正好是从0开始数的第几位,这样不是和poj2828的排队问题一样了吗?根据优先级大小选择更新位置,只不过上一题是进队,所以逆序,这一题出队就是正序啊!这样就通过相对位置找到了实际位置,也就和卡片数字有了联系。接着就是递推,这里的递推式有点复杂,只不过多了一次+n)%n而已,本质是不变的,顺时针和逆时针的区别我们直接把负数加上去就好。再有,约瑟夫环的处理必须是0~n-1,所以刚开始k需减一。还有顺时针遍历时,看别人说是因为他自己本身要走,所以减一,而逆时针就不需要了(看懂了吗?反正我是没懂。。。泣不成声)。


这题要注意的点简直不要太多,我一口老血差点吐出来。。再见


#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>

using namespace std;

const int N = 500010;
const int INF = 1e8;

struct line
{
    int l, r;
    int len;
}tree[4*N];

int num[N], maxnum, maxid;

void prime(int n)
{
    memset(num, 0, sizeof(num));
    for(int i = 1; i <= n; i++)
    {
        num[i]++;
        for(int j = i*2; j <= n; j+=i)
        {
            num[j]++;
        }
    }
    maxnum = num[1];;
    maxid = 1;
    for(int i = 2; i <= n; i ++)
    {
        if(maxnum < num[i])
        {
            maxnum = num[i];
            maxid = i;
        }
    }
}

void build(int i, int l, int r)
{
    tree[i].l = l;
    tree[i].r = r;
    tree[i].len = r-l+1;
    if(l == r)
    {
        return;
    }
    int mid = (l + r) >> 1;
    build(i*2, l, mid);
    build(i*2+1, mid+1, r);
}

int query(int i, int pos) //由相对位置找到实际位置
{
    tree[i].len--;
    if(tree[i].l == tree[i].r)
    {
        return tree[i].l;
    }
    if(pos <= tree[i*2].len) query(i*2, pos);//注意这里是<=因为线段从1开始
    else query(i*2+1, pos-tree[i*2].len);
}

char name[N][15];//这两个不能定义在main里面,不然无法编译
int val[N];

int main()
{
 //   freopen("in.txt", "r", stdin);
    int n, k;
    while(~scanf("%d%d", &n, &k))
    {
        prime(n);
        memset(val, 0, sizeof(val));
        for(int i = 1; i <= n; i++)
        {
            scanf("%s%d", name[i], &val[i]);
        }
        build(1, 1, n);
        int pos;
        for(int i = 0; i < maxid; i++)
        {
            pos = query(1, k);
            n--;
            if(n == 0) break;
            if(val[pos] > 0) k = ((k-1+val[pos]-1)%n+n)%n+1;
            else k = ((k-1+val[pos])%n+n)%n+1;
        }
        printf("%s %d\n", name[pos], maxnum);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值