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; }