poj 2886(约瑟夫+单点修改+反素数)

题意:有n个小孩围成一圈,编号从1到n,每个人手上都有一个纸片,上面数字是num[i],开始第k个人离开,然后按第k个人纸片上的数字num[k],如果是正数,下一个离开的是左边第num[k]个人,负数是右边第-num[k]个人离开,游戏一直持续到最后一个人离开。第p个出去的人都会得到p的约数个数的糖果,问谁得到的糖果数最多,输出人名和约数个数。
题解:开始不知道F(p)是什么,后来查了下才知道是反素数,打了素数表后,知道了n就知道第几个出去的得到的糖果最多,那么接下来就是求得到糖果数量最多的人的名字。用线段树维护每个区间的人数,然后用约瑟夫的递推公式f[i] = (f[i - 1] + m) % i可以得到下一个离开的人的序号。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500005;
int antip[] = {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};
int pnum[] = {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};
int sum[N << 2];
char name[N][15];
int num[N];

void pushup(int k) {
    sum[k] = sum[k * 2] + sum[k * 2 + 1];
}

void build(int k, int left, int right) {
    if (left == right) {
        sum[k] = 1;
        return;
    }
    int mid = (left + right) / 2;
    build(k * 2, left, mid);
    build(k * 2 + 1, mid + 1, right);
    pushup(k);
}

int modify(int k, int left, int right, int p) {
    if (left == right) {
        sum[k] = 0;
        return left;
    }
    int mid = (left + right) / 2, temp;
    if (p <= sum[k * 2])
        temp = modify(k * 2, left, mid, p);
    else
        temp = modify(k * 2 + 1, mid + 1, right, p - sum[k * 2]);
    pushup(k);
    return temp;
}

int main() {
    int n, k;
    while (scanf("%d%d", &n, &k) == 2) {
        int m = 0;
        while (antip[m] <= n)
            m++;
        int res = pnum[m - 1];
        m = antip[m - 1];
        for (int i = 1; i <= n; i++)
            scanf("%s%d", name[i], &num[i]);
        build(1, 1, n);
        int cur = k, cnt = n;
        while (m--) {
            k = modify(1, 1, n, cur);
            cnt--;
            if (!m)
                break;
            if (num[k] > 0)
                cur = (cur - 1 + num[k] - 1) % cnt + 1;
            else
                cur = ((cur - 1 + num[k]) % cnt + cnt) % cnt + 1;
        }
        printf("%s %d\n", name[k], res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值