POJ2886 Who Gets the Most Candies?(反素数+线段树模拟约瑟夫环)

传送门:http://poj.org/problem?id=2886
这题真是太厉害了。。
我们要先知道反素数,推荐: http://blog.csdn.net/ACdreamers/article/details/25049767
这题很多人都是打表反素数的,我暴力预处理了一下,虽然有点慢,最后也是4000+ms过的。。
这题的答案一定为反素数,因为小于这个反素数的数的因子数一定比反素数少,所以求出<=n的最大的反素数,就一定是1-n中因子数最多的数字。知道这个结论以后,用线段树模拟一下约瑟夫环的跳转过程。

if(val[pos]>0){
    k=(((k-1+val[pos]-1)%mod)+mod)%mod+1;
    //k-1表示去掉本身,val[pos]-1在后面+1回来是为了防止k=0
}
else{
     k=(((k+val[pos]-1)%mod)+mod)%mod+1;
     //往前找的时候k不用去掉,所以k不用-1
 }

然后每次把k更新到线段树里,类似于POJ 2828 排队那题

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define mp make_pair
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define calm (l+r)>>1
const int INF = 1e9+7;

const int maxn=500010;

bool vis[110];
vector<int> prime;
int antiprime[300],num[300];
void init(){//打表反素数
    for(int i=2; i<=50; i++){
        if(!vis[i]){
            prime.pb(i);
            for(int j=i+i; j<=50; j+=i){
                vis[j]=true;
            }
        }
    }
    memset(antiprime,127,sizeof antiprime);
    antiprime[1]=1;
    for(int i=1;i<=maxn;i++){
        int ans=1;
        for(int j=0,len=prime.size(); j<len; j++){
            if(i%prime[j]==0){
                int tot=1,t=i;
                while(t%prime[j]==0){
                    t/=prime[j];tot++;
                }
                ans*=tot;
            }
            antiprime[ans]=min(antiprime[ans],i);
        }
    }
    sort(antiprime,antiprime+300);
    for(int i=0;i<300;i++){
        if(antiprime[i]==antiprime[299])break;
        int ans=1;
        for(int j=0,len=prime.size(); j<len; j++){
            if(antiprime[i]%prime[j]==0){
                int tot=1,t=antiprime[i];
                while(t%prime[j]==0){
                    t/=prime[j];tot++;
                }
                ans*=tot;
            }
        }
        num[i]=ans;
    }
    int now=0;
    for(int i=0;i<300;i++){
        if(antiprime[i]==antiprime[299]){break;}
        if(num[i]<=now)antiprime[i]=antiprime[299];
        now=max(now,num[i]);
    }
    sort(antiprime,antiprime+300);
    for(int i=0;i<300;i++){
        if(antiprime[i]==antiprime[299]){break;}
    }
    for(int i=0;i<300;i++){
        if(antiprime[i]==antiprime[299])break;
        int ans=1;
        for(int j=0,len=prime.size(); j<len; j++){
            if(antiprime[i]%prime[j]==0){
                int tot=1,t=antiprime[i];
                while(t%prime[j]==0){
                    t/=prime[j];tot++;
                }
                ans*=tot;
            }
        }
        num[i]=ans;
    }
}

int n,k;
char name[maxn][13];
int val[maxn];
int sum[maxn<<2];
void build(int l,int r,int rt){
    sum[rt]=r-l+1;
    if(l==r){
        return;
    }
    int m=calm;
    build(lson);build(rson);
}
int update(int x,int l,int r,int rt){
    sum[rt]--;
    if(l==r)return l;
    int m=calm;
    if(x<=sum[rt<<1])return update(x,lson);
    return update(x-sum[rt<<1],rson);
}
int main()
{
    //freopen("D://input.txt","r",stdin);
    init();
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%s%d",name[i],&val[i]);
        }
        int cnt=0;
        for(int i=0;i<35;i++){
        //答案一定为反素数,因为小于这个反素数的数的因子数一定比反素数少
            if(antiprime[i]<=n)cnt=i;
        }
        //printf("%d\n",antiprime[cnt]);
        build(1,n,1);
        int &mod=sum[1];//圈内总人数
        int pos=0;
        val[0]=0;//先等于0,使第一个人为k
        for(int i=0;i<antiprime[cnt];i++){
            if(val[pos]>0){
                k=(((k-1+val[pos]-1)%mod)+mod)%mod+1;
                //k-1表示去掉本身,val[pos]-1在后面+1回来是为了防止k=0
            }
            else{
                k=(((k+val[pos]-1)%mod)+mod)%mod+1;
                //往前找的时候k不用去掉,所以k不用-1
            }
            pos=update(k,1,n,1);
        }
        printf("%s %d\n",name[pos],num[cnt]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值