poj 2886 Who Gets the Most Candies?(线段树+二分+反素数)

106 篇文章 0 订阅
34 篇文章 0 订阅

题意:

n个小朋友围成一圈,每个小朋友有自己的名字以及一张卡片,卡片上的数字a代表在自己出列后想让自己右边第a个人下一个出列,如果a<0就是左边第a个人。第m个小朋友第一个出列。每个小朋友能获得的糖果数量=f(p),p为他的出列顺序,f(p)表示能整除p的数的个数。


解题思路:

暴力来的话是n^2的,由于n是5e5,所以肯定不行。

我们可以用一个线段树维护一下区间内剩余的数的个数,然后我们通过转换a,把下一个要出列的人在当前剩下的序列里的编号求出来,然后我们去query这个编号在原序列上的位置,这时候就要二分一下了,其实线段树上的二分非常好想,因为区间都划好了,反而很形象。找到在原序列的位置后,就更新一下,把这个点去掉。这样我们就获得了每个小朋友的出列顺序,也就是p。

然后求出最大的f(p)就需要用一下反素数了,反素数:

对于任何正整数x,其约数的个数记做g(x).例如g(1)=1,g(6)=4.如果某个正整数x满足:对于任意i(0<i<x),都有g(i)<g(x),则称x为反素数·

那么我们只需要找到小于等于n的最大的反素数,它的因子个数就一定是1-n里最多了的。

然后1-5e5的反素数只要打个表就行,我直接复制了金桔的数组啦....

此处立个学一下高效求反素数的flag,不知道有没有用...

代码:

#include <cstdio>
#define lson o<<1
#define rson o<<1|1

using namespace std;
const int maxn=5e5+5;
int order[maxn];

struct p
{
    int l, r;
    int x;
}val[maxn<<4];

int hug[40]={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,
    554400};
int frac[40]={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,216};
struct node
{
    char nam[11];
    int nex;
}a[maxn];

void build(int o, int l, int r)
{
    if(l==r)
    {
        val[o].l=val[o].r=l;
        val[o].x=1;
        return;
    }
    int mid;
    mid=(l+r)/2;
    build(lson, l, mid);
    build(rson, mid+1, r);
    val[o].l=l, val[o].r=r, val[o].x=r-l+1;
    return;
}
void update(int o, int l, int r, int x)
{
    if(l==r)
    {
        val[o].x=0;
        return;
    }
    int mid=(l+r)/2;

    if(x<=mid)update(lson, l, mid, x);
    else update(rson, mid+1, r, x);
    val[o].x=val[lson].x+val[rson].x;

    return;

}

int query(int o, int l, int r, int x)
{
    if(l==r)
    {
        return l;
    }
    int res;
    int mid=(l+r)>>1;
    if(val[lson].x>=x)
    {
        res=query(lson, l, mid, x);
    }
    else
    {
        res=query(rson, mid+1, r, x-val[lson].x);
    }
    return res;
}

int main()
{
    int n, m, i, j;
    while(~scanf("%d%d", &n, &m))
    {

        for(i=1; i<=n; i++)
        {
            scanf("%s%d", a[i].nam, &a[i].nex);
        }
        build(1, 1, n);

        int len=0;
        for(i=1; i<=n; i++)
        {
            int id=query(1, 1, n, m);
            update(1, 1, n, id);
//            printf("%d\n", id);
            order[++len]=id;
            int mod=n-i;
            if(mod==0)break;
            if(a[id].nex>0)m=((m-1+a[id].nex)%mod+mod)%mod;
            else m=((m+a[id].nex)%mod+mod)%mod;
            if(m==0)m=mod;

        }
//        for(i=1; i<=n; i++)
//        {
//            printf("%s %d\n", a[i].nam, i);
//        }
        for(i=0; i<36; i++)
        {
            if(hug[i]>n)break;
        }
        n=i-1;
        int ans=order[hug[n]];
        printf("%s", a[ans].nam);
        printf(" %d\n", frac[n]);

    }

    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值