POJ2886 (线段树)

题目链接

题意:有n个小朋友顺时针围着坐了一圈,每个人手里都有一个数字。游戏从第k个小朋友开始,首先,第k个小朋友退出游戏,如果退出游戏的小朋友手上的数字是正整数A,则从他的左手边的第A个小朋友退出游戏,如果退出游戏的小朋友手上的数字是负整数-A,则从他的右手边的第A个小朋友退出游戏(注意,因为顺时针,左手边就是往后,右手边就是往前)。第i个退出的小朋友可以获得a(i的约数的个数)的糖果,问最多糖果的小朋友的名字,并且他有多少糖果。

刚开始看这题题目的时候,哇。。看不懂。。最后求的东西不知道是什么,上网搜了一下大佬的题解,才知道,求的好像是翻素数。。。但是。。。重点在于。。我不会啊。。。所以还是老老实实初始化了一下,然后说这题是用线段树做,一直没懂线段树要怎么用上去,看了题解才明白。。。太菜了太菜了。。

思路:先初始化一下5e5+10每个数字的约数有几个(大佬就直接打表反素数。。),我们可以反过来想,可以用类似于素数打表的那个。接着用线段树去求每个小朋友的位置。这题线段树用的比较特殊(代码注释),在于修改的部分其它都差不多。

反素数:对于任何正整数x,其约数的个数记作g(x)。例如g(1)=1、g(6)=4。如果某个正整数x满足:g(x)>g(i) 0<i<x,则称x为反素数。例如,整数1,2,4,6等都是反素数。

AC代码:

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

const int maxn = 5e5+10;
struct people{
    char name[10];
    int num;
}p[maxn];     //每个小朋友的名字和手上的数字

int prime[maxn],node[maxn<<2];     //prime:记录每个数字的约数。 node:线段树的结点

void init(){    //初始化
    int i, j;
    memset(prime, 0, sizeof(prime));
    for(i = 1; i <= maxn; i++){
        prime[i]++;
        for(j = i*2; j <= maxn; j+=i)
            prime[j]++;
    }
}

void build(int l,int r,int rt){     //普通线段树建树
    if(l == r){
        node[rt] = 1;
        return;
    }
    int m = l+r>>1;
    build(l, m, rt<<1);
    build(m+1, r, rt<<1|1);
    node[rt] = node[rt<<1] + node[rt<<1|1];
}

int update(int pos,int l,int r,int rt){    //pos在剩下来的人中的位置
    if(l == r){
        node[rt] = 0;
        return l;
    }
    int m = l+r>>1,ans;
    if(pos <= node[rt<<1])      //这里node表示此区间的人数,如果pos小的话,所以要找的人在前面一个区间。
        ans = update(pos, l, m, rt<<1);
    else     //如果pos大于前面一个区间的人数,则pos在后面一个区间,但是我们要减去前一个区间的人数。
        ans = update(pos - node[rt<<1], m+1, r, rt<<1|1);
    node[rt] = node[rt<<1]+node[rt<<1|1];
    return ans;
}

int main(){
    int n, k, Max, ans, i;
    init();    
    while(scanf("%d%d",&n,&k)!=EOF){
        for(i = 1; i <= n; i++){
            scanf("%s%d",p[i].name,&p[i].num);
        }
        build(1, n, 1);
        Max = 0,ans = 0;
        for(i = 1; i <= n; i++){    //找出前n个数,反素数最大的是哪个并记录下来
            if(prime[i] > Max){     
                ans = i;            //记录位置
                Max = prime[i];     //记录最多的糖果
            }
        }
        int pos, now = 0, mov;
        while(true){
            pos = update(k, 1, n, 1);
            if(++now == ans)       //到达此位置,退出循环
                break;
            mov = p[pos].num;
            if(mov > 0)
                k = (k - 1 + mov - 1)%node[1] + 1; //我们现在往后走,但是原本的第k个小朋友已经退出游戏了,所以现在的第k是原来的k+1个,所以我们要先-1。后面的-1为防止出现0。
            else
                k = ((k - 1 + mov)%node[1]+node[1])%node[1] + 1;  //现在往前走,所以第一个-1就不用了。
        }
        printf("%s %d\n",p[pos].name,Max);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhuyou_/article/details/81194473
今日推荐