题意:
约瑟夫环,每次转移的步数根据跳出那个孩子手中牌的数字决定,可以有负数.
求第p个跳出的孩子的名字 以及p的约数个数
p为不大于n的最大反素数
反素数...制表..
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
#define REP(i,n) for(int i=0;i<(n);++i)
#define FOR(i,l,h) for(int i=(l);i<=(h);++i)
#define FORD(i,h,l) for(int i=(h);i>=(l);--i)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
#define N 500010
int tree[N<<2];
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,665280};
const int factorNum[]={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};
struct child
{
char name[15];
int val;
}c[N];
void build(int l,int r,int rt)
{
tree[rt]=r-l+1;//区间长度为键值,表示空余位置
if(l==r) return;
int m=(l+r)>>1;
build(lson);
build(rson);
}
int update(int p,int l,int r,int rt)
{
tree[rt]--;//一路减
if(l==r) return l;//返回找到的位置
int m=(l+r)>>1;//这个位置是通过键值找到的,线段树就是在这里可以加速.像是二分,而且它可以集成
if(p<=tree[rt<<1]) return update(p,lson);//存一段区间上的总和或最值
return update(p-tree[rt<<1],rson);
}
int main(void)
{
int n,k,&mod=tree[1];//mod是根节点的引用,代表剩余孩子的总数
while(scanf("%d%d",&n,&k)==2)
{
FOR(i,1,n) scanf("%s%d",c[i].name,&c[i].val);
build(1,n,1);
FOR(i,1,7) cout<<tree[i]<<" ";
cout<<endl;
int cnt=0;
while(cnt<35 && antiprime[cnt]<=n) cnt++;
cnt--;
int pos=0;
c[pos].val=0;
REP(i,antiprime[cnt])//antiprime[cnt]即p
{///添加与删除等价!!!
if(c[pos].val>0)
{//各种取模其实是为了凑数啊...
k=((k+c[pos].val-2)%mod+mod)%mod+1;
cout<<endl;
}
else //涉及到循环必用取模
{//既可以理解成他前面的位置是不变的,又可以理解成mod减少了1(他自己不在了,所以mod用mod+1替代)
k=((k+c[pos].val-1)%mod+mod)%mod+1;
cout<<endl;
}
pos=update(k,1,n,1);
}//如果用数组直接模拟的话,这个孩子还在不在总是要访问一下才知道,所以复杂度O(n^2)(等差数列求和1..n-1)
//用线段树模拟时,总能够避开已经占用的位置.
//线段树中的pos按照剩下的孩子原先的编号重新排序.所以仍然是找到前面有多少空位置之后的那个.!
printf("%s %d\n",c[pos].name,factorNum[cnt]);
}
return 0;
}