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

来源:互联网 发布:mac安装jdk 编辑:程序博客网 时间:2024/06/05 04:57
Who Gets the MostCandies?

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/herhand. The game starts from the K-th child, who tells all theothers the integer on his card and jumps out of the circle. Theinteger on his card tells the next child to jump out. Let Adenote the integer. If A is positive, the next child will bethe A-th child to the left. If A is negative, thenext 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 getF(p) candies where F(p) is the numberof positive integers that perfectly divide p. Who gets themost candies?

Input

There are several test cases inthe 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 withmagnitudes within 108) on their cards in increasingorder of the children’s numbers, a name and an integerseparated by a single space in a line with no leading or trailingspaces.

Output

Output one line for each test case containing the name of theluckiest child and the number of candies he/she gets. If tiesoccur, always choose the child who jumps out of the circlefirst.

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]);
 }

 return0;
}

0 0
原创粉丝点击