[POJ2886] 谁得到最多糖果 - 反质数+线段树

16 篇文章 0 订阅
15 篇文章 0 订阅

题目描述

有N个孩子顺时针坐成一个圆圈且从1到N编号,每个孩子手中有一张标有非零整数的卡片。第K个孩子先出圈,如果他手中卡片上的数字A大于零,下一个出圈的是顺时针第A个孩子。否则,下一个出圈的是逆时针第(-A)个孩子。第p个出圈的孩子会得到F(p)个糖果,F(p)为p的因子数。求得到糖果数最多的是哪个孩子及得到多少糖果。


输入格式

输入包含多组测试数据,每组测试数据的第一行为两个整数N(0 < N ≤ 500,000)和K(1 ≤ K ≤ N),接下来N行,每行一个字符串(最多10个字母)和一个非零整数(绝对值小于等于10^8),分别表示小孩的名字和手中的数字。


输出格式

对于每组数据输出仅一行为得到糖果数最多的是哪个孩子的姓名和得到的糖果数。


样例数据

样例输入

4 2
Tom 2
Jack 4
Mary -1
Sam 1

样例输出

Sam 3


题目分析

不知道为什么爆搜找最大的反质数会WA
此题第一眼约瑟夫环
第二眼卧槽500000不超才怪
第三眼可以事先算出得到糖果数最多糖果数和操作次数
怎么预先算出呢?
题目问的是n内最大约数个数的数,即为反质数。
可以爆搜(尚未调出),也可以打表。
接下来做的就是模拟了。
普通的模拟肯定要超时,所以要用线段树维护这个约瑟夫环。
用线段树记录left~right尚存的人数。
在build时预先初始化为right-left+1,之后动态询问时依次-1
这样便将时间降到了nlogn
在当前环剩下的人中下一个人的位置是可以算出来的
如果v>0 k=((k-1+v-1)%mod+mod)%mod+1
如果v<0 k=((k-1+v)%mod+mod)%mod+1
其中mod是当前环中尚存的人数

当然也可以用树状数组


源代码

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
    int num=0,bj=1;
    char x=getchar();
    while(x<'0'||x>'9') {
        if(x=='-')bj=-1;
        x=getchar();
    }
    while(x>='0'&&x<='9') {
        num=num*10+x-'0';
        x=getchar();
    }
    return num*bj;
}
const int maxn=500005;
struct Tree {
    int left,right,sum;
};
struct Segment_Tree {
    Tree tree[maxn*4];
    void build(int index,int Left,int Right) {
        tree[index].left=Left;
        tree[index].right=Right;
        tree[index].sum=Right-Left+1;
        if(Left==Right)return;
        int mid=(Left+Right)/2;
        build(2*index,Left,mid);
        build(2*index+1,mid+1,Right);
    }
    int Query(int index,int target) { //返回target的下标 
        tree[index].sum--;
        if(tree[index].left==tree[index].right)return tree[index].left;
        if(tree[index*2].sum>=target)return Query(index*2,target); //左儿子点数够在左儿子中找
        else return Query(index*2+1,target-tree[index*2].sum); //否则往右儿子找 
    }
};
int cnt[37]= {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,500001};  
int Ans[37]= {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,1314521}; 
int Found_Number(int n) {
    int i;
    while(cnt[i]<=n)i++; //表内寻找 
    return i;
}
struct BearKid {
    char Name[15];
    int data;
} a[500005];
Segment_Tree st;
int n,k;
int main() {
    while(scanf("%d%d",&n,&k)!=EOF) {
        int id=Found_Number(n);
        int ans=cnt[id-1],Max=Ans[id-1];
        st.build(1,1,n);
        for(int i=1; i<=n; i++)scanf("%s %d",a[i].Name,&a[i].data);
        int pos=0; //k代表当前环里面的人(剔除了出环) pos代表原来序列中的人(没有剔除) 
        for(int i=1; i<=ans; i++) {
            if(a[pos].data>0)k=((k-1+a[pos].data-1)%st.tree[1].sum+st.tree[1].sum)%st.tree[1].sum+1; //算出下一个人的位置(mod是当前环中的人数) 
            else k=((k-1+a[pos].data)%st.tree[1].sum+st.tree[1].sum)%st.tree[1].sum+1;
            pos=st.Query(1,k);
        }
        printf("%s %d\n",a[pos].Name,Max);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值