poj2886 (约瑟夫环,树状数组)

n个人围成圈,每个人手上有一个数字,最开始k跳出圈,如果k手上的数num是正数,则向左num个人出圈,负数就向右,直到所有人都出圈,得分最高的是第(1-n中因子数最多的数)次跳出的人

首先处理1-n中因子最多的数,有个名词叫反素数,不管他这里直接打表

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
const int mx = 6e5 + 5;
int cnt[mx], anti_prim[mx], num[mx], tot = 0;

int main() {
    freopen("out.txt", "w", stdout);
    memset(cnt, 0, sizeof(cnt));
    for (int i = 1; i <= 600000; i++) {
        for (int j = 1; j*j <= i; j++) {
            if (i % j == 0) {
                if (j*j == i) cnt[i] += 1;
                else cnt[i] += 2;
            }
        }
    }
    int max_num = 0;
    for (int i = 1; i <= 600000; i++) {
        if (cnt[i] > max_num) {
            anti_prim[++tot] = i;
            num[tot] = cnt[i];
            max_num = cnt[i];
        }
    }
    for (int i = 1; i <= tot; i++) {
        printf("%d,",anti_prim[i]);
    }
    for (int i = 1; i <= tot; i++) {
        printf("%d,",num[i]);
    }

    return 0;
}

 

接着是处理跳出次序问题,用树状数组记录每个位置的人是否已经跳出,刚开始看到题解说二分我以为是把人按1-n排列,第k个人跳出就从k开始向左或向右进行二分出下一个跳出的人的位置,但是这样会有问题,比如第k个人手上是5,那么应该向右二分出5个人,但是有可能右边不足五个人需要绕回到左边,所以这样是行不通的

正确的做法是把人按1-n排列,然后每次有人跳出就通过公式计算出下一个人是从1-n中还在圈内的第几个,这样就可以二分1-n了

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

using namespace std;
const int mx = 5e5 + 5;
int anti_prim[] = {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 num[] = {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 n, k, c[mx];

struct node {
    char name[20];
    int value; 
}stu[mx];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int val) {
    for (int i = x; i <= n; i+=lowbit(i)) {
        c[i] += val;
    }
}

int sum(int x) {
    int ans = 0;
    for (int i = x; i > 0; i-=lowbit(i)) {
        ans += c[i];
    }
    return ans;
}

int bit_search(int x) {
    int left = 1, right = n, mid;
    while (left < right) {
        mid = (left + right) / 2;
        if (sum(mid) >= x) right = mid;
        else left = mid + 1;
    }
    return left;
}

int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    while(scanf("%d%d", &n, &k) != EOF) {
        memset(c, 0, sizeof(c));

        for (int i = 1; i <= n; i++) {
            scanf("%s %d", stu[i].name, &stu[i].value);
            add(i, 1);
        }
        int pos = lower_bound(anti_prim, anti_prim + 36, n) - anti_prim;
        if (anti_prim[pos] > n) pos--;
        int now = k, next = k, remaining = n - 1, cnt = 1, ans;
        add(k, -1);
        while (true) {
            if (cnt == anti_prim[pos]) {
                ans = now;
                break;
            }
            int m = stu[now].value;
            if (m > 0) next = (next - 1 + m) % remaining;
            else next = ((next + m) % remaining + remaining) % remaining;
            if (next == 0) next = remaining;
            remaining--; cnt++;
            now = bit_search(next);
            add(now, -1);
        }
        printf("%s %d\n", stu[ans].name, num[pos]);
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值