lightoj1097 lucky number 报数类问题 线段树的单点更新

来源:互联网 发布:类似maka软件 编辑:程序博客网 时间:2024/05/25 23:56

题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1097

题目:

Lucky numbers are defined by a variation of the well-known sieve of Eratosthenes. Beginning with the natural numbers strike out all even ones, leaving the odd numbers 1, 3, 5, 7, 9, 11, 13, ... The second number is 3, next strike out every third number, leaving1, 3, 7, 9, 13, ... The third number is 7, next strike out every seventh number and continue this process infinite number of times. The numbers surviving are called lucky numbers. The first few lucky numbers are:

1, 3, 7, 9, 13, 15, 21, 25, 31, 33, ...

In this problem your task is to find the nth lucky number where n is given in input.

Input

Input starts with an integer T (≤ 10000), denoting the number of test cases.

Each case contains an integer n (1 ≤ n ≤ 105).

Output

For each case, print the case number and the nth lucky number.

Sample Input

Output for Sample Input

2

2

100000

Case 1: 3

Case 2: 1429431



分析:这个题给我一种约瑟夫环的问题的感觉,但人家约瑟夫是一个环,算了我们姑且叫它报数问题。这种报数问题感觉就是一个在剩余序列中的相对位置和在原序列中绝对位置的关系。我们用sum数组存储在某个区间上剩余元素的个数,我们发现线段树这一数据结构可以很好的解决这一类问题。

上面这道题中,我们发现实质上每次需要报的数就是从剩余序列中相对位置为2,3,... ...的数对应都在原序列中绝对位置的数。算了好长一句话还是看代码吧!唔,不过这代码还不完善,因为边界没处理好,如果大佬有解决方法恳求指点。

code:

#include<cstdio>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int MAXN=2e6;
int sum[(MAXN+5)<<2];int ans[MAXN],visit[MAXN];
//sum是用来维持某区间上剩余数的个数,叶子节点也是区间,只不过左右区间重合了
void PushUp(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
    if(l==r){
        sum[rt]=l&1;//因为原始的序列没有偶数,只有奇数
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    PushUp(rt);
}
//通过在剩余序列中的相对位置p,去原序列中找到绝对位置l,并删除它
int update(int p,int l,int r,int rt){
    if(l==r){
        sum[rt]=0;
        return l;
    }
    int pos;
    int m=(l+r)>>1;
    if(sum[rt<<1]>=p)pos=update(p,lson);
    else pos=update(p-sum[rt<<1],rson);
    PushUp(rt);
    return pos;
}


//功能与上面的update类似,只不过只询问,不删除
int Query(int p,int l,int r,int rt){
    if(l==r){
        return l;
    }
    int pos;
    int m=(l+r)>>1;
    if(sum[rt<<1]>=p)pos=Query(p,lson);
    else pos=Query(p-sum[rt<<1],rson);
    return pos;
}
void init(){//打表,初始化
    build(1,MAXN,1);
    memset(visit,0,sizeof(visit));
    memset(ans,0,sizeof(ans));
    for(int i=2;i<=MAXN;++i){//i为每次报的数在剩余序列中的相对位置
        if(i>sum[1])return;//现在根本没那么多人辣!根节点表示剩余序列中还有多少人!
        int k=Query(i,1,MAXN,1);//查询相对位置i的绝对位置
        int ID=k;//需要删除的人的编号
        for(;;){
            int pos=update(ID,1,MAXN,1);
            visit[pos]=1;//visit数组记录这个人已经被删除了,方便后来打表
            if(pos+ID>MAXN)break;//每次单向循环的终止条件没处理好。
            ID=ID+k-1;//删除一个人后第ID个人,第n(n>ID)个人就变成了第n-1个人
        }
    }
}
int main(void){
    //打表
    init();
    for(int i=1,j=1;i<=MAXN;++i){
        if((i&1)&&!visit[i]){
            ans[j++]=i;
        }
    }


    int T;scanf("%d",&T);
    int CASE=0;
    while(T--){
        int k;scanf("%d",&k);
        printf("Case %d: %d\n",++CASE,ans[k]);
    }
}


0 0
原创粉丝点击