POJ 2886 Who Gets the MostCandies?(线段树+模拟+求数的约数个数)

POJ 2886 Who Gets the MostCandies?(线段树+模拟+求数的约数个数)

http://poj.org/problem?id=2886

题意:

     n个孩子按顺时针排列,每个人手上都有一张牌,牌上有一个数字,从第k个孩子开始出队,出队的孩子卡上数字是val,则从他开始顺时针第val人是下一个出队的,负数则逆时针数那个第val个人,第P个出队的会得到的糖果数是p的因子个数,输出得到最多糖果的人和他的糖果数,如果有多个,则输出最先出队的人。

分析:

        其实本题就是模拟每轮游戏,然后在每一轮游戏中先算出当前需要出队的第j个孩子,然后用线段树找到还在队中的第j个还是得原始编号。

        首先建立一棵线段树,其每个叶节点都是1(代表每个孩子都在圈中,如果第i个孩子不在圈中,那么就令第i个叶子(孩子的编号不是i而是i管理的区间l或rsum=0)。

分析题中的例子:

4 2

Tom 2

Jack 4

Mary -1

Sam 1

       假设我们第一个出去的孩子是第2个孩子,那么我们把第二个叶节点的值置为0,该步只需要通过update找到sum值正好为2的那个区间的右端点(且该右端点的sum值也为1)即可。然后让这个sum变为0,表示这个孩子出了圈。并且我们读到了JACK的val值为4,当前圈剩余3个人,所以我们向后找4%3=1个人,如果[3,n]内的sum和>=1,那么直接在JACK右边即[3,n]区间找即可。如果sum的和<1,那么就在JACK左边[1,1]内找1-sum的那个位置的1.

       依次类推即可。线段树的每个叶节点维护name,val,sum三个值,非叶节点只需要维护sum值即可。

AC代码:1672ms

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 500000+100;
#define lson i*2,l,m
#define rson i*2+1,m+1,r
int sum[MAXN*4];
int cnt;
struct node
{
    char name[10];
    int val;
}nodes[MAXN];
void PushUp(int i)
{
    sum[i]=sum[i*2]+sum[i*2+1];
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        sum[i]=1;
        return ;
    }
    int m=(l+r)/2;
    build(lson);
    build(rson);
    PushUp(i);
}

int update(int q,int i,int l,int r)//找到第q个sum为1的叶节点,并返回其在线段树中的节点编号
{
    if(l==r)
    {
        sum[i]=0;
        return l;
    }
    int m=(r+l)/2;
    int res;
    if(q<=sum[2*i]) res= update(q,lson);
    else  res= update(q-sum[2*i],rson);
    PushUp(i);
    return res;
}
int f[500010];//f[i]=x表示i的约数有x个
void get_f()
{
    int i,j;
    for(i=1;i<=500000;i++)
    for(j=1;i*j<=500000;j++)
    {
        f[i*j]++;//假设x=i*j,那么i*j和j*i时,f[x]都会++,正好是加上了i和j各一次。
    }
}
int main()
{
    int n,k;
    memset(f,0,sizeof(f));
    get_f();
    while(scanf("%d%d",&n,&k)==2&&n&&k)
    {
        build(1,1,n);
        for(int i=1;i<=n;i++)
        {
            scanf("%s%d",nodes[i].name,&nodes[i].val);
        }
        if(n==1)
        {
            printf("%s 1\n",nodes[1].name);
            continue;
        }
        int j=k;
        int ans=0;
        char ans_name[10];
        cnt=n;//cnt为当前有效叶节点的总数
        for(int i=1;i<=n;i++)
        {
            //printf("j=%d\n",j);
            int num=update(j,1,1,n);//num是指原始序列的第num个孩子,j是指线段树区间[1,n]内的第j个1
            cnt--;
            int x= f[i];
            int v=nodes[num].val;
            if(ans<x)
            {
                ans = x;
                strcpy(ans_name,nodes[num].name);
            }
            if(i==n)
                break;
            int l_num=j-1;
            int r_num=cnt-j+1;
            if(v<0)//用v值来得到下一个需要处理的j值
            {
                v=(-v)%cnt;
                if(v==0)v=cnt;
                if(l_num>=v) j =l_num-v+1;
                else j=cnt-(v-l_num)+1;
            }
            else if(v>0)
            {
                v=v%cnt;
                if(v==0)v=cnt;
                if(r_num>=v)j=l_num+v;
                else j=v-r_num;
            }

        }
        printf("%s %d\n",ans_name,ans);
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值