[POJ 2886] Who Gets the Most Candies? (Joseph环问题 + 树状数组)

POJ - 2886

有 N个人顺时针围成一圈,刚开始第 K个人退出圈,他手上有个数字 A_k
如果 A_k为正,则他左数 A_k个人退出,如果 A_k为负,则他右数 A_k个人退出
同样,接下来退出的那个人手上也有个数字,一直进行下去直到圈内所有人都离开
第 p个人退出的时候,他能得到 F(p)个糖果,F(p) 为 p约数的个数
问得到最多糖果的最早退出的人是谁,他得到了几个糖果


这是个很经典的问题,叫做 Josep环问题,以前我只会模拟求
虽然现在也只会模拟求但利用树状数组,可以使得这个过程变得很高效

维护一个树状数组,每个点存的是每个人的现在的序号
当第 i个人退出时,其后所有人向前移一位,序号减一 ( bit.add(i,-1) )
然后总人数再减一,并计算下一个人在新的环中的序号
下一次在树状数组中二分查找这个序号的下界,然后循环

用样例来说明,现在有四个人:1 2 3 4
第一次第二个人退出后,第二位以后减一:1 1 2 3
然后下一个人的序号为 (1+4)%3=2
二分查找这个人在树状数组中的位置,即为第 3个
然后重复这个过程:1 1 1 2

关于题目提到的要求的答案,其实就是不超过 N的最大的反素数
所以其实可以先把反素数 M求出来,然后进行到第 M次就行了,不必模拟到底

由于树状数组从 1开始标号,所以取模的时候有诸多不便
再加上每个人的偏移量会有负数,所以存在一些坑点
1) 当偏移量为负数时,要加上 1
2) 取模会有负数,要 +tot然后再 %tot
3) 如果模的结果为 0,要手动变为 tot

#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#include <set>
#include <queue>
using namespace std;
typedef pair<int,int> Pii;
typedef long long LL;
typedef unsigned long long ULL;
typedef double DBL;
typedef long double LDBL;
#define MST(a,b) memset(a,b,sizeof(a))
#define CLR(a) MST(a,0)
#define Pow2(a) (a*a)

const int maxn=5e5+10;
const int tprm[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
struct BIT
{
    int *Bit;int siz;
    BIT(int size):siz(size){Bit=new int[size+1];memset(Bit,0,sizeof(int)*(size+1));}
    ~BIT(){delete []Bit;}
    int lowbit(int x){return x&-x;}
    void add(int x, int v)
    {
        while(x<=siz)
        {
            Bit[x]+=v;
            x+=lowbit(x);
        }
    }
    int sum(int x)
    {
        int res=0;
        while(x>0) // Indexed start from 1
        {
            res+=Bit[x];
            x-=lowbit(x);
        }
        return res;
    }
    void update(int x,int v){add(x,v);add(x+1,-v);}
    void update(int l,int r,int v){add(l,v);update(r+1,-v);}
    int query(int x){return sum(x);}
};

int N,K;
char name[maxn][15];
int nxt[maxn];
LL aprm,aprmc;

void get_aprm(int,int,LL,LL,LL); //anti_prime

int main()
{
    bool first=1;
    while(~scanf("%d%d", &N, &K))
    {
        BIT bit(N);
        for(int i=1; i<=N; i++)
        {
            scanf(" %s %d", name[i], &nxt[i]);
            bit.update(i,i);
        }
        aprm=1e18,aprmc=0;
        get_aprm(0,63,1,1,N);
        int ans=aprmc,M=aprm,p=K,tot=N,l=K,r;
        for(int i=1; i<M; i++)
        {
            tot--;
            bit.add(l,-1);
            if(nxt[l]<0) nxt[l]+=1;
            p=((bit.query(l)+nxt[l])%tot+tot)%tot;
            if(p==0) p=tot;
            l=1,r=N;
            while(l<r)
            {
                int mid=(l+r)>>1;
                if(bit.query(mid)>=p) r=mid;
                else l=mid+1;
            }
        }
        printf("%s %d\n", name[l], ans);
    }
    return 0;
}

void get_aprm(int np,int top,LL fcnt,LL now,LL LimN)
{
    if(fcnt>aprmc){aprm=now;aprmc=fcnt;}
    if(fcnt==aprmc&&now<aprm){aprm=now;}
    if(np>=16) return;
    for(int i=1; i<=top; i++)
    {
        if(now>LimN/tprm[np]) break;
        now*=tprm[np];
        get_aprm(np+1,i,fcnt*(i+1),now,LimN);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值