线段树 + 反素数 poj2886

 关于反素数:摘自此博客:http://magicode.blog.sohu.com/120450550.html

这个题主要用到线段树的思想,每次推算出要出去的人在当前剩下的人中的排位,再用线段树求出其原来的编号,即可算出每次应该出去的人,该人得到的糖果数为f(p)(p为出去的顺序,f(p)为p约数的个数

其实当总人数n确定时,p的值和f(p)的值就确定了,p为小于等于n的最大反素数

新学一个反素数的概念

反素数n,1<=i<n,则f(i)<f(n),即在1-n中,n的约数最多

由于反素数个数不多,这个题可以打表,把1-500000之间的反素数先算出来

反素数有以下两个性质

性质一:一个反素数的质因子必然是从2开始连续的质数.

性质二:p=2^t1*3^t2*5^t3*7^t4.....必然t1>=t2>=t3>=....

这两个性质都比较好证明

所以可以用递归的方法把反素数求出来

代码:

//反素数
#include <iostream>
#include <cmath>
using namespace std;

//32位整数的反素数只能由这几个素数构成
int prime[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 }; 
int bestNum; //反素数
__int64 bestSum;//反素数的约数个数

//当数为num时,其约数个数为sum,本次对第k个素数的幂进行选取,
//幂最高只能选limit, 求比n小的最大的反素数
void search(int num, __int64 sum, int k, int limit, int n)
{
    if (k >= 9) return;

    if (sum > bestSum) { //更新
        bestSum = sum;
        bestNum = num;
    }
    else if (bestSum == sum && bestNum > num) //约数个数相同时,选取较小的数
        bestNum = num;

    int i, newNum = num;
    for (i = 1; i <= limit; i++) //第k个素数取i次方
    {
        newNum *= prime[k];
        if (newNum > n) break;
        search(newNum, sum * (i + 1), k + 1, i, n); //第k+1个素数最多取i次,根据上面的第二条性质
    }
}

int main()
{
    int n = 500000;

    cout << (int)log2(n * 1.0) << endl;
    int i = 0;
    while (n > 0)
    {
        i++;
        bestNum = 1; bestSum = 1;
        search(1, 1, 0, (int)log2(n * 1.0), n);
        cout << "{" << bestNum << ", " << bestSum << "}, ";
        n = bestNum - 1;
    }
    cout << i << endl;
    return 0;
}

题意:输出第p个退出环的人的编号,使得p的约数最多。

解题:p的值为小于n的最大的反素数,因为此时p的约数最多。

#include <iostream>
#include <cstdio>
using namespace std;

#define N 500005
//反素数表
int antiprime[35][2] = {
{498960, 200}, {332640, 192}, {277200, 180}, {221760, 168}, {166320, 160}, {110880, 144},
{83160, 128}, {55440, 120}, {50400, 108}, {45360, 100}, {27720, 96}, {25200, 90},
{20160, 84}, {15120, 80}, {10080, 72}, {7560, 64}, {5040, 60}, {2520, 48}, {1680, 40},
{1260, 36}, {840, 32}, {720, 30}, {360, 24}, {240, 20}, {180, 18}, {120, 16}, {60, 12},
{48, 10}, {36, 9}, {24, 8}, {12, 6}, {6, 4}, {4, 3}, {2, 2}, {1, 1} };

int sum[N << 2], num[N]; //sum数组存储区间内的人数
char name[N][12];

void pushUp(int rt)
{
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

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

void update(int p, int l, int r, int rt)
{
    if (l == r) {
       sum[rt] = 0; return;
    }
    int m = (l + r) >> 1;
    if (m >= p) update(p, l, m, rt << 1);
    else update(p, m + 1, r, rt << 1 | 1);
    pushUp(rt);
}

int query(int k, int l, int r, int rt)
{
    if (l == r) return l;

    int m = (l + r) >> 1;
    if (sum[rt << 1] >= k) return query(k, l, m, rt << 1);
    else return query(k - sum[rt << 1], m + 1, r, rt << 1 | 1);
}

int main()
{
    int i, n, k, pos, p, candy;

    while (scanf ("%d %d", &n, &k) != EOF)
    {
        i = 0;
        while (n < antiprime[i][0]) i++;
        p = antiprime[i][0];
        candy = antiprime[i][1];

        for (i = 1; i <= n; i++)
            scanf ("\n%s %d", name[i],  &num[i]);

        build (1, n, 1);
        i = 1; pos = k; //k为人在约瑟夫环中的相对位置,pos为该人在最初的环中的绝对位置
        while (i < p) {
            update (pos, 1, n, 1); //删除当前这个人
            //求出下一个人的在约瑟夫环中的相对位置
            k += num[pos];
            if (num[pos] > 0) k--; //注意:当num[pos] > 0时,pos后面的人的相对位置都要减1
            k = (k % sum[1] + sum[1]) % sum[1]; //sum[1]为剩余人的人数
            if (k == 0) k = sum[1];
            //由相对位置求出绝对位置
            pos = query(k, 1, n, 1);
            i++;
        }
        printf ("%s %d\n", name[pos], candy);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值