POJ2886_Who Gets the Most Candies?_反素数|树状数组与Joseph问题

Who Gets the Most Candies?
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 14420 Accepted: 4557
Case Time Limit: 2000MS

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 ≤ K ≤ N) 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

N 名小朋友手里各拿着一张纸,围成一个圈,从 1 开始顺时针编号。每次把一个小朋友拉出圈。规定第一个出圈的是编号为 K 的小朋友,然后从他的位置出发顺时针数他手上的数字,对数到的哪个小朋友进行相同的操作。小朋友手上的数字可以是负数,这表示逆时针数。第 p 个出圈的小朋友将获得 g(p) 块糖,其中 g(p) 等于 p 的因子个数。问获得糖块最多的小朋友的名字和他获得的糖块数。


一、反素数

首先要找到 1 到 n 中因子数最大的那个数。一开始的想法是暴力打表然后维护一个树状数组查询最大值,可惜超时了。无奈之下百度一发,发现了反素数这个东西。

对于任何正整数x,其约数的个数记做g(x).例如g(1)=1,g(6)=4.如果某个正整数x满足:对于任意i(0<i<x),都有g(i)<g(x),则称x为反素数。

对于这个题可以首先打一个反素数表,同时记录每个反素数对应的因子个数。根据 n 的规模,只需求前 35 个反素数,为保险起见求到 37 个。对于每个 n 只需要找到一个小于等于它的最大的反素数o,则第o个离队的小朋友获得糖块数最多。

关于反素数有两条性质:

性质一:一个反素数的质因子必然是从2开始连续的 质数.
性质二:p=2^t1*3^t2*5^t3*7^t4.....必然t1>=t2>=t3>=....
ACM中常见的反素数的题型主要有两种:

(1)给定一个数,求一个最小的正整数 x ,使得约数个数为 n

(2)求出 1 到 n 中约数个数最多的这个


二、树状数组与约瑟夫问题

维护一个树状数组记录人数的前缀和。如果一个人出队了,就在这个人的原始位置-1。查找当前队列的某个值时,只需要枚举树状数组的前缀和,就可以找到这个人在原始队列中的位置。


有两点注意的地方:

1.枚举前缀和的时候要用二分优化,否则会TLE。

2.求下次出队人的位置时,有一个重要特判。


#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
#define f(x) x.first
#define s(x) x.second

using namespace std;
typedef long long LL;

//反素数表
const int aprime[37]={1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,
                       1260,1680,2520,5040,7560,10080,15120,20160,25200,
                       27720,45360,50400,55440,83160,110880,166320,221760,
                       277200,332640,498960,554400,665280
                      };

//反素数对应的因子个数
const int fact[37]={1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,
                       64,72,80,84,90,96,100,108,120,128,144,160,168,180,
                       192,200,216,224
                      };

int n, k;
const int maxn = 500000 + 100;
const int inf = 500000;
char Name[maxn][20];	//名字
int  Val [maxn];		//卡片上的数字
int  Bit [maxn];		//树状数组

//树状数组单点修改
void Add(int i, int v)
{
	while(i<= n)
	{
		Bit[i] += v;
		i += i & -i;
	}
}

//树状数组求前缀和
int Sum(int i)
{
	int res = 0;

	while(i)
	{
		res += Bit[i];
		i -= i & -i;
	}

	return res;
}

//利用反素数表求出 n 下因子最多的那个数以及其因子个数
int Getm(int n)
{
	return lower_bound(aprime, aprime+37, n) - aprime;
}

//求当前的第 p 人是原始的第几人
//通过枚举人数的前缀和实现
//利用了二分优化
int Back(int p)
{
	int lb = 1, ub = n;
	while(lb <= ub)
	{
		int md = (lb + ub) >> 1;
		if(Sum(md) >= p) ub = md - 1;
		else lb = md + 1;
	}
	return ub + 1;
}

int main()
{
	while(scanf("%d %d", &n, &k) != EOF)
	{
		//得到 n 对应的最大反素数即其因子数
		int t = Getm(n);
		if(aprime[t] != n) t--;

		for(int i= 1; i<= n; i++)
			scanf(" %s %d", Name[i], &Val[i]);

		//树状数组初始化
		//一开始所有的人都在队列中
		memset(Bit, 0, sizeof(Bit));
		for(int i= 1; i<= n; i++)
			Add(i, 1);

		int cnt = n;

		for(int i= 1; i<= aprime[t]; i++)
		{
			// x 是 i 在原始队列中的位置
			int x = Back(k);

			if(i == aprime[t])
			{
				printf("%s %d\n", Name[x], fact[t]);
				break;
			}

			//这个人出队
			Add(x, -1);
			cnt --;

			//计算下一个人现在是第几个
			int temp = Val[x];
			if(temp > 0) temp --;

			k = (k + (temp % cnt) + cnt) % cnt;
			if(k == 0) k = cnt;//重要特判
		}
	}

	return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值