题意 :n个小盆友从1到n编号按顺时针编号,然后从第k个开始出圈,他出去之后如果他手里的牌是x,如果x是正数,那下一个出圈的左手第x个,如果x是负数,那出圈的是右手第-x个,游戏中第p个离开的孩子得到的糖果是f(p)个,f(p)是p的约数的个数。
思路 : 因为f(p)是p的约数的个数,所以,要用到反素数,反素数的定义及求法。反素数s的约数个数比小于它的数的约数个数都多,最大反素数就是要找到的那个得到糖果最多的人。线段树记录的是还剩多少 人未出列。
#include <iostream> #include <stdio.h> #include <string.h> using namespace std; const int maxn = 506000 ; char ch[maxn][15] ; int data[maxn] ; struct mode { int l,r ; int value,sum ; }tree[maxn * 4] ; //反素数表 int rev[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,500001}; //反素数对应的约数个数 int revs[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,1314521}; void build(int v,int l,int r) { tree[v].l = l ; tree[v].r = r ; tree[v].value = r-l+1 ; if(l == r) return ; int mid = (l + r) >> 1 ; build(v*2,l,mid) ; build(v*2+1,mid+1,r) ; } int query(int v,int x) { tree[v].value-- ; if(tree[v].l == tree[v].r) return tree[v].l ; if(x <= tree[v*2].value) return query(v*2,x) ; else return query(v*2+1,x-tree[v << 1].value) ; } int main() { int n, k; while(scanf("%d %d",&n,&k) != EOF) { int i = 0,maxx = 0,p = 0 ; while(rev[i] <= n) i++ ; p = rev[i-1] ; maxx = revs[i-1] ; build(1,1,n ) ; for(int j = 1 ; j <= n ; j++) scanf("%s %d",ch[j],&data[j]) ; int yy ; for(int i = 0 ; i < p ; i++) { n -- ; yy = query(1,k) ; if(n == 0) break ; if(data[yy] > 0) k = (k-1+data[yy]-1) % n + 1 ; else k =( (k-1+data[yy])%n+n) % n+1 ; } printf("%s %d\n",ch[yy],maxx) ; } return 0; }