N 个小孩围成一圈,他们被顺时针编号为 1 到 N。每个小孩手中有一个卡片,上面有一个非 0 的数字,游戏从第 K 个小孩开始,他告诉其他小孩他卡片上的数字并离开这个圈,他卡片上的数字 A 表明了下一个离开的小孩,如果 A 是大于 0 的,则下个离开的是左手边第 A 个,如果是小于 0 的,则是右手边的第 -A 个小孩。游戏将直到所有小孩都离开,在游戏中,第 p 个离开的小孩将得到 F(p) 个糖果,F(p) 是 p 的约数的个数,问谁将得到最多的糖果。输出最幸运的小孩的名字和他可以得到的糖果。
//以上的翻译是从网上找的
首先呢,第P个小孩将得到P的约数个糖果,这就要引入一个数学名词,反素数,反素数的性质就是,比他小的数的约数个数都没他多,那么我们只要在1到n的范围内找到最大的反素数就能知道最多的糖果数,这个其实用反证法就能证明,假设p是1到n内的最大反素数,那么,如果在p到n内还有数的约数比p多,那么显然p就不能算是最大的反素数了,所以我们就先把50万以内的反素数提前打表,他们的约数也提前打好表,这样的话,就不用所有人都出来后才得出最终结果了。
假设p是1到n内最大的反素数,我们只需模拟p次这个出队过程了,然后有一个需要注意的地方是,下一个人的位置怎么求,这里面有一种是相对位置,还有一种是最初位置,很显然,由于数据量太大,不能通过简单的模拟来实现最初位置和相对位置的转化,这里面,能直接求出来的是相对位置,假设一个人的位置是k,手上卡片的数字是+m,那么下一个人的位置应该是p+m,但是由于他的出队,这个相对位置就要减1了,是p+m-1,但是如果一个人的卡片是-m,此时他的退出就对下一个人的相对位置没有影响了,因为下一个人的位置应该是p-m,在这个人之前,就没有影响了。然后需要注意的是可能会出现负数和0,这就要适当的处理一下了。
然后就是相对位置与最初位置的转换,这里用到了线段树,线段树所附加的信息是该区间还有多少人,每踢掉一个人,区间值变化一次,然后就相当于一个动态的队列了,中间踢掉某个人,整个序列的长度减1,每个人的位置发生了变化,然后再踢相对位置为p的人,只要从头数到第p个人,踢掉就行了。线段树的话,我们先从叶子节点看,踢掉一个人,就相当于这个叶子节点的值就变成0了,然后从1到n,数叶子节点为1的个数,到p的时候把踢掉该位置的人就行了,这样就好理解点了,然后根据点更新到段就能实现快速的插入了。
//以上是摘自http://blog.csdn.net/sdj222555/article/details/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define maxn 500005
#define ls o<<1
#define rs o<<1|1
#define MID (l+r)>>1
const int antiprime[] = {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};
const int factor[] = {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};
struct line_tree{
int l,r;
int cnt,value;
char name[10];
}tree[maxn<<3];
inline void push_up(int o)
{
tree[o].cnt = tree[ls].cnt + tree[rs].cnt;
}
void build(int l,int r,int o=1)
{
tree[o].l = l;
tree[o].r = r;
if(l==r){
tree[o].cnt = 1;
scanf("%s %d",tree[o].name,&tree[o].value);
return;
}
int mid = MID;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(o);
}
line_tree update(int x,int o=1)
{
tree[o].cnt--;
if(tree[o].l==tree[o].r){
return tree[o];
}
if(x<=tree[ls].cnt)return update(x,ls);
else return update(x-tree[ls].cnt,rs);
//push_up(o);
}
int main()
{
int n,k;
while(scanf("%d%d",&n,&k)==2){
build(1,n);
int best;
for(int i=0;antiprime[i]<=n;i++){
best=i;
}
line_tree te;te.value=0;
for(int i=1;i<=antiprime[best];i++){
if(te.value>0){
k = ((k + te.value- 2) % tree[1].cnt + tree[1].cnt) % tree[1].cnt + 1;
//由于要从下一个人开始数,并且k要减一,所以减2,其实是 k-1+te.value-1
}
else{
k = ((k + te.value - 1) % tree[1].cnt + tree[1].cnt) % tree[1].cnt + 1;
}
te = update(k);
}
printf("%s %d\n",te.name,factor[best]);
}
return 0;
}