题目链接:http://poj.org/problem?id=2886
题目大意:n个小朋友站成一个圈做游戏,每个小朋友手中有一个非零数字num(整数代表从这点起顺时针数num个人,负数代表逆时针数num个人),游戏开始时第k个人出列,然后下一个出列的人为上一个出列人手中数字所指的人,以此类推,直到所有人都出列为止。F(p)为第p个出列的人得到的糖果数量,定义F(p)为p的约数的个数,找出得到糖果数量最多的小朋友的姓名,和得到的糖果数(如果有多人得到相同数量的糖果,则选择最先出列的人)。
分析:F(p)的定义很明确的指出了得到的最多糖果数即为n以内的最大的反素数的因子个数,这个值是由n唯一确定的,那么接下来我们只需要找出第一个得到最大糖果数的小朋友的编号,我们设这个最大的反素数为p,那么我们要找的就是第p个出列的人。
打反素数RPrime表和反素数的因子数fact表代码如下:
#include<stdio.h> typedef long long ll; const int prime[16]= {1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47}; int maxsum, bestnum, n; int RPrime[500005],fact[500005],cnt; void getantiprime(int num, int k,int sum,int limit) {//num:当前枚举到的数,k:枚举到的第k大的质因子;sum:该数的约数个数;limit:质因子个数上限; ll temp; if(sum > maxsum) { maxsum = sum; bestnum = num; //如果约数个数更多,将最优解更新为当前数; } if(sum==maxsum && bestnum > num) bestnum = num; //如果约数个数相同,将最优解更新为较小的数; if(k > 15) return; temp = num; for(int i=1; i<=limit; i++) //开始枚举每个质因子的个数; { if(temp*prime[k] > n) break; temp = temp * prime[k]; //累乘到当前数; getantiprime(temp, k+1, sum*(i+1), i); //继续下一步搜索; } } int main() { cnt=0; for(n=1;n<=500000;n++) { maxsum=0; bestnum=0; getantiprime(1,1,1,50); if(bestnum!=RPrime[cnt-1]) { RPrime[cnt]=bestnum; fact[cnt++]=maxsum; } } for(int i=0;i<cnt;i++) printf("%d %d\n",RPrime[i],fact[i]); return 0; }
实现代码如下:
#include <cstdio> #include <iostream> using namespace std; const int maxn=500005; 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, }; 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 }; struct node { char name[15]; int num; }child[maxn]; struct segment { int l,r,sum; //sum表示该区间有的人数 }tree[maxn<<2]; int ans_num; //纪录第p个出列的人的编号 void build(int root,int l,int r) { tree[root].l=l; tree[root].r=r; if(l==r) { tree[root].sum=1; return ; } int mid=(l+r)>>1; build(root<<1,l,mid); build(root<<1|1,mid+1,r); tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum; } void update(int root,int pos) { tree[root].sum--; if(tree[root].l==tree[root].r) { ans_num=tree[root].l; return ; } if(pos<=tree[root<<1].sum) update(root<<1,pos); else update(root<<1|1,pos-tree[root<<1].sum); } int main() { int n,k,p; while(scanf("%d%d",&n,&k)!=-1) { for(int i=1;i<=n;i++) scanf("%s%d",child[i].name,&child[i].num); for(int i=0;RPrime[i]<=n;i++) p=i; //找出n以为的最大反素数RPrime[p] build(1,1,n); ans_num=0; for(int i=0;i<RPrime[p];i++) { int mod=tree[1].sum; //根结点的sum值即为当前的总人数 if(child[ans_num].num>0) //num值大于0,顺时针方向找下一个人 k=((k+child[ans_num].num-2)%mod+mod)%mod+1; else k=((k+child[ans_num].num-1)%mod+mod)%mod+1; update(1,k); } printf("%s %d\n",child[ans_num].name,fact[p]); } return 0; }