线段树 + 反素数 poj2886

来源:互联网 发布:mac win10 文件共享 编辑:程序博客网 时间:2024/06/13 01:54

 关于反素数:摘自此博客: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;}


原创粉丝点击