POJ2886-Who Gets the Most Candies?-模拟约瑟夫环+反素数表(线段树上二分+爆搜打表)

来源:互联网 发布:sim800c gprs接收数据 编辑:程序博客网 时间:2024/05/18 08:12

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 __int64const 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|1int 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);elseupdate(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);elsereturn 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;}elseif(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 whosum--;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;}


0 0
原创粉丝点击