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
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个离队的小朋友获得糖块数最多。
关于反素数有两条性质:
(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;
}