ACM: 线段树 poj 2886 约瑟夫问题

Who Gets the Most Candies?

Description

N children are sitting in a circle to play a game.

The children are numbered from 1 to N in clockwise order. Each of them has a card with a non-zero integer on it in his/her hand. The game starts from the K-th child, who tells all the others the integer on his card and jumps out of the circle. The integer on his card tells the next child to jump out. Let A denote the integer. If A is positive, the next child will be the A-th child to the left. If A is negative, the next child will be the (A)-th child to the right.

The game lasts until all children have jumped out of the circle. During the game, the p-th child jumping out will get F(p) candies where F(p) is the number of positive integers that perfectly divide p. Who gets the most candies?

Input

There are several test cases in the input. Each test case starts with two integers N (0 < N ≤ 500,000) and K (1 ≤ KN) on the first line. The next N lines contains the names of the children (consisting of at most 10 letters) and the integers (non-zero with magnitudes within 108) on their cards in increasing order of the children’s numbers, a name and an integer separated by a single space in a line with no leading or trailing spaces.

Output

Output one line for each test case containing the name of the luckiest child and the number of candies he/she gets. If ties occur, always choose the child who jumps out of the circle first.

Sample Input

4 2
Tom 2
Jack 4
Mary -1
Sam 1

Sample Output

Sam 3

 

题意: 经典的约瑟夫问题, 但是这次第p个出局的人计算获得candy, 就是题目中提出的F(p):

      p的约数的个数. 要求出最大的那个出局的人和约数个数.

解题思路:

      1. 先看问题: 约瑟夫问题. 可以使用线段树的高效删除, 同样可以在log(N)的时间复杂度(平均),

         用一个len域来记录下当前这个段区间中还有多少人在圈内.

      2. 二分查找删除的圈内的人:

         (1). 当左子树人数pt[pos*2].len >= num时, 就是要删除的人, 即是前一个出队列的人留下的

              下一个出对人得位置大于当前段区间的左子树时, 就要归到左子树中, 因为在左子树内就

              查找到相应的人, del(num, pos*2, c); //num:人数, pos*2:左子树, c:第几个人.

         (2). 否则, 要归到右子树中, 因为左子树的人数不够了, 自然会数到右子树上,

              del(num-pt[pos*2].len, pos*2+1, c); //num-pt[pos*2].len: 减去左子树已经数过的人数.

         其实你可以想象成, 在线段树, 把一个圈内的人分到左右子树上了, 在删除圈内的人中逐渐从

         子树中删除节点, 这样不仅可以高效的进行模拟, 也可以再删除的过程中记录下需要的信息,

         一举两得.(想了好久)

      3. 怎么计算p的约数的个数, 因为题目是有范围的,(0 < N ≤ 500,000), 只需要计算出在这个范围

         内p的约数的个数. 方法很容易跟打素数表相似.

         for(int i = 1; i < MAX; ++i)
        {
            anti_prime[i]++;
            for(int j = i*2; j < MAX; j += i)
                anti_prime[j]++; //每次都加上i, 自然i都是可以整除j的.
        }

 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define MAX 500005

struct person
{
 char name[12];
 int num;
}p[MAX];

struct node
{
 int l, r;
 int len;
}pt[MAX*4];

int n, k;
int anti_prime[MAX];
int len, best, count;
int result;

void init()
{
 memset(anti_prime, 0, sizeof(anti_prime));
 for(int i = 1; i < MAX; ++i)
 {
  anti_prime[i]++;
  for(int j = i*2; j < MAX; j += i)
   anti_prime[j]++;
 }
}

void buildTree(int l, int r, int pos)
{
 pt[pos].l = l, pt[pos].r = r;
 pt[pos].len = (r-l+1);
 if(l == r) return ;
 int mid = (l+r)/2;
 buildTree(l, mid, pos*2);
 buildTree(mid+1, r, pos*2+1);
}

void del(int num, int pos, int c)
{
 pt[pos].len--;
 if(pt[pos].l == pt[pos].r)
 {
  if(best == c) result = pt[pos].l;
  if(n-c == 0) return ;
  if(p[ pt[pos].l ].num > 0) count--;
  count = ( (count+p[ pt[pos].l ].num) % (n-c) + (n-c) ) % (n-c);
  if(count == 0) count = n-c;

  return ;
 }

 if(num <= pt[pos*2].len)
  del(num, pos*2, c);
 else
  del(num-pt[pos*2].len, pos*2+1, c);
 
}

int main()
{
 init();
// freopen("input.txt","r",stdin);
 while(scanf("%d %d",&n, &k) != EOF)
 {
  int i;
  best = 1;
  for(i = 1; i <= n; ++i)
  {
   scanf("%s %d",p[i].name, &p[i].num);
   if(anti_prime[best] < anti_prime[i])
    best = i;
  }

  buildTree(1, n, 1);
  count = k;
  for(i = 1; i <= n; ++i)
   del(count, 1, i);

  printf("%s %d\n",p[result].name, anti_prime[best]);
 }

 return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值