#include <cstdio>
#include <iostream>
#include <algorithm>
#define lson L , m , rt << 1
#define rson m + 1 , R , rt << 1 | 1
using namespace std;
const int maxn = 520000;
struct Node
{
int L,R;
int sum;
}tree[maxn*4];
int n,k;
int RPrime[]=
{//反素数
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
};
int fact[]=
{//反素数约数个数
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
};
void builttree(int L,int R,int rt)
{
tree[rt].L=L;
tree[rt].R=R;
tree[rt].sum=R-L+1;
if(L==R)
return ;
int m=(L+R)/2;
builttree(lson);
builttree(rson);
}
int ans[maxn];
int Query(int rt,int pos)
{
tree[rt].sum--;
if(tree[rt].L==tree[rt].R)
{
return tree[rt].L;
}
int m=(tree[rt].L+tree[rt].R)/2;
if(tree[rt*2].sum>=pos)
Query(rt*2,pos);//左孩子可以容下则进左孩子
else
Query(rt*2+1,pos-tree[rt*2].sum);//左孩子已满
}
char strname[maxn][20];
int val[maxn];
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
builttree(1,n,1);
int p;
int maxx=0;
for(int i=1;i<=n;i++)
{
scanf("%s %d", &strname[i], &val[i]);
}
int i=0;
while(1)
{
if(RPrime[i]>n)
{
p=RPrime[i-1];
maxx= fact[i-1];
break;
}
i++;
}
int indx;
for(int i=0;i<p;i++)
{
n--;
indx=Query(1,k);
if(n==0)break;
if(val[indx]>0)
k=(k-1+val[indx]-1)%n+1;
else
k=((k-1+val[indx])%n+n)%n+1;
}
cout<<strname[indx]<<' '<<maxx<<endl;
}
return 0;
}
题意:
N个孩子顺时针坐成一个圆圈且从1到N编号,每个孩子手中有一张标有非零整数的卡片。第K个孩子先出圈,如果他手中卡片上的数字A大于零,下一个出圈的是他左手边第A个孩子。否则,下一个出圈的是他右手边第(-A)个孩子。第p个出圈的孩子会得到F(p)个糖果,F(p)为p的因子数。求得到糖果数最多的是哪个孩子及得到多少糖果。
分析:
出圈过程有点类似约瑟夫环。假设当前出圈的是剩余孩子中的第K个,他手中的数字为A。若A大于零,下一个出圈的就应该是剩余孩子中的第(K-1+A-1)%n+1个;若A小于零,下一个出圈的就应该是剩余孩子中的第((K-1+A)%n+n)%n+1个。问题的关键是如何求得出圈孩子的原始位置,这里我用的是线段树。线段树的每个节点的sum存储了所在区间还有多少孩子留下,查询节点rt中第num个孩子的原始位置时,如果num<=st[rt<<1].sum,则在左孩子节点中查询第num个孩子的原始位置;否则在右孩子节点中查询第num-st[rt<<1].sum个孩子的原始位置。
另外,本题还用到了反素数。反素数的定义:对于任何正整数x,其约数的个数记做g(x)。例如g(1)=1,g(6)=4。如果某个正整数x满足:对于任意i(0<i<x),都有g(i)<g(x),则称x为反素数。对于本题,设p为不大于N的反素数,则第p个出圈的孩子得到的糖果最多,为p的约数个数。