http://poj.org/problem?id=2886
题意,就是让你模拟约瑟夫环,
给n,m,n是人数,m是第一个出局的人
给数组a[],a[i]表示第i个人出局后,下一个出局的人是i的位置往右数第a[i]个人(如果a[i]<0就是往左数)
让你求出出局的人中,他的出局序号的约数个数最大的一个人,如果个数相同输出最早的一个
输出名字和约数个数
也就是1-n里 约数个数最大的,且最早出现的,这就是反素数的定义嘛,
n<=5e5,先用根据反素数性质,爆搜打表,发现只有几十个数,就直接存起来啦。
反素数的求法可以参考http://blog.csdn.net/viphong/article/details/50782312
然后对于每个case的n,先在反素数数组里二分找到对应的反素数k,然后也就是说,我们只需要模拟k次约瑟夫环的删除操作,然后就可以得到名字了
至于模拟的话,我们可以用线段树实现
开始的思路一直是先算出要往左(右)移动k步,然后希望用线段树来模拟移动k步。。。
后来发现这样思路是不对的,应该这样,假设前有n个孩子,执行当前删除操作后就剩下n-1个孩子,直接计算出要移动到第几个孩子身上,然后才在线段树上二分找到,整棵树里,剩余的 第k个孩子的编号
update的话只需要用到单点更新,不需要lazy
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
//#include <set>
#include <vector>
#include <iostream>
using namespace std;
#define ll __int64
const double pi=acos(-1.0);
double eps=1e-5;
double max(double a,double b)
{return a>b?a:b;}
double min(double a,double b)
{return a<b?a:b;}
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
int n,m;
const int maxn=500000+50;
struct tree
{
int sum[maxn<<2] ;
void PushUP(int rt)
{
sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
if (l == r)
{
sum[rt]=1;
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUP(rt);
}
void update(__int64 l,__int64 r,__int64 rt,__int64 num,__int64 val)
{
if (l == r&& r==num)
{
sum[rt]=val;
return ;
}
__int64 m = (l + r) >> 1;
if (num<=m)
update(lson,num,val);
else
update(rson,num,val);
PushUP(rt);
}
int query (int qL,int qR,int l,int r,int rt) //rt是节点编号
{
if(l > qR || qL > r)
return 0;
if (qL <= l && r <= qR)
return sum[rt];
int m = (l + r) >> 1;
return query (qL , qR , lson)
+ query (qL , qR , rson);
}
int find_next ( int l,int r,int rt,int num)//线段树上二分查找
{
if (l==r) return r;
int mid=(l+r)>>1;
if (sum[rt<<1]>=num)
return find_next(l,mid,rt<<1,num);
else
return find_next(mid+1,r,rt<<1|1,num-sum[rt<<1]);
}
};
tree tp;
int tm[500005];
char name[500005][15];
int prime[]={1,2,3,5,7,11,13,17};//2*3*5*7*11*13*17>500000
ll ans; /*
void dfs(int k, ll now,ll cnt,int last)//求1-n最大反素数,初始化ans=n,num=1
//k是层数,now是当前的乘积,cnt是当前的数对应的因子数,last是最后一个因子的最高次数
{
if (now>n) return; //剪枝,比判k==17快
//if (k==17) return ;
if ( cnt>num) //更新答案
{ ans=now;num=cnt;}
else
if(cnt==num)
{
if (now<ans)
ans=now;
}
ll t=prime[k],i; //t是素因子的方幂
for (i=1;i<=last;i++) //last是最高次,显然当前素因子最高次不超过前面的最高
{
if (t>n/now) break; //乘法溢出
dfs(k+1,now*t,cnt*(i+1),i);
t*=prime[k];
}
}*/
//因为反素数比较少,直接先打表出来。。。
int ff[50]={
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 num[50]={
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,
};
int main()
{
int i;
while(cin>>n>>m!=NULL)
{
for (i=1;i<=n;i++)
{
scanf("%s %d",name[i],&tm[i]);
}
tp.build(1,n,1);
int it=upper_bound(ff,ff+35,n)-ff-1;
ans=ff[it];//找第ans个人,其糖果为num
int who =m; //当前删除操作对象
int sum=n; //总人数
int p=m; //下一对象的偏移值
int cur=m;
for (i=1;i<ans;i++)
{
tp.update(1,n,1,who,0); //del who
sum--;
p=tm[who]; //偏移量
int next_one;
if (p<0) //left
{
p=-p;
p%=sum;
next_one=(cur-p+sum)%sum;<span style="white-space:pre"> </span>//下一个出局的人是第几个人
if (!next_one) next_one=sum;
}
else
{
p%=sum;
next_one=(cur-1+p)%sum;
if (!next_one) next_one=sum;
}
cur=next_one;
who=tp.find_next(1,n,1,next_one);//找到第next个人的实际下标
}
printf("%s %d\n",name[who],num[it]);
}
return 0;
}