ACM练级日志:POJ 2886 约瑟夫环,线段树和反素数

我昨天才知道模拟约瑟夫环是可以用线段树来解的……

不妨假设总共有N个人,他们的编号是1~N(这个编号很重要,影响到后面的推导)

怎么解呢?我们用一棵线段树,每个节点记录一下这个区间还剩下多少人,一开始当然就是R-L+1个人了。

然后我们要做的事情就是,每次先求出这次要出列的“绝对位置”,然后用线段树查一查这个绝对位置上站的是谁。

所谓“绝对位置”,就是1~N,不会变的,比如第一个出列的是2,那么绝对位置2那里站的应该是3,下次当你看到绝对位置2要出列的时候,就知道实际上要出列的人应该是3了。


以POJ 2886的样例为例:

 
绝对位置1 2 3 4
实际站的人12(+4)34
 1 3(-1)4
 1(out)  4
    4(最后留下)
 


 有了这个概念之后,我们就可以用线段树求解“一个绝对位置上的人站的究竟是谁”的问题了。非常简单,如果我问x号上面站的是谁,那么我就看看左边是不是够x个,如果够x个,说明答案肯定在左子树里,否则就去右子树找x- node[p*2].count 即可。最后找到叶子节点就是所求的人。


找到以后删除之就是典型的单点删除操作,非常简单。


这道题还多多少少用到了一点反素数…… 反素数是说,一个数x是反素数,当且仅当所有比x小的数i,i的约数个数都小于x的约数个数。显然对于一个给定的n,最接近n的反素数就是我们要找的目标。比如n是5,那么我们就要找到第4个出队的人是谁。这样,由于50w以内的反素数十分有限,我们可以提前打表,然后知道n以后立刻找出目标反素数。当然暴力从复杂度上讲也没问题……


代码中还有一些细节问题,在注释里说吧。


#include<iostream>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#define D(x) cout<<#x<<" "<<x<<endl
using namespace std;
 
int n,k;
 
struct ntype
{
    int l,r;
    int count;
} node[2000200];
 
struct stype
{
    char namae[20];
    int v;
};
stype student[500010];
 
int max_candy = 0;
 
const int antiprime[] = {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};  // 反素数
     
const int factor[] = {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};  // 反素数的约数个数
 
void del(int s, int e, int p, int tar) // 线段树单点删除tar号人
{
    node[p].count--;
    if(s==e)
        return;
         
    int mid = (s+e)/2;
    if(tar<=mid)
        del(s,mid, p*2, tar);
    else
        del(mid+1, e, p*2+1, tar);
         
    return;
}
 
int ask(int s, int e, int p, int q)// 询问q上站的是谁
{
    if(s==e)
    {
        return s;
    }
     
    int mid = (s+e)/2;
     
    if(node[p*2].count >= q)
        return ask(s, mid, p*2, q);
     
    else
        return ask(mid+1, e, p*2+1, q - node[p*2].count);
}
 
 
int prepare() // 准备好目标反素数和他的约数个数
{
    int ret_p = upper_bound(antiprime, antiprime + 36, n) - 1 - antiprime;
     
    //D(ret_p);
    int ret = antiprime[ret_p];
    max_candy = factor[ret_p];
    return ret;
}
 
void build(int s, int e, int p)
{
    node[p].l=s;
    node[p].r=e;
    node[p].count= e-s+1;
     
    if(s==e)
        return;
     
    int mid = (s+e)/2;
    build(s,mid, p*2);
    build(mid+1, e, p*2+1);
     
    return;
}
 
void init()
{
    memset(node,0,sizeof(node));
    memset(student, 0, sizeof(student));
    max_candy = 0;
    return;
}
 
int main()
{
    while(scanf("%d %d", &n,&k)!=EOF)
    {
        init();
         
        int i;
        for(i=1;i<=n;i++)
        {
            scanf("%s %d", student[i].namae, &student[i].v);
        }
         
        build(1,n,1);
         
        int end = prepare();
         
        int now_absolute=k;
        int now_real;
        for(i=1;i<=end;i++)
        {
            //D(now_absolute);
            now_real = ask(1,n,1, now_absolute);
            //D(now_real);
             
            del(1,n,1,now_real);
            if(i==n)
                break;
             
            if(student[now_real].v > 0)
            {
                now_absolute = now_absolute - 1 -1 + student[now_real].v; // 第一个-1是因为我们从1开始标号,要先-1;第二个-1是因为我们要往右数,必须先退一格
                now_absolute %= n-i;
                now_absolute = (now_absolute + (n-i) ) % (n-i);
                now_absolute +=1; // +1 是为了恢复成1~N
            }
            else
            {
                now_absolute = now_absolute - 1 + student[now_real].v; //第一个-1原因同上,由于我们是让右边的人靠过来,所以往左数不必再-1
                now_absolute %= n-i;
                now_absolute = (now_absolute + (n-i) ) % (n-i); // 取模避免出现负数的方法:先模,然后加,再模
                now_absolute += 1;
            }
        }
         
        printf("%s %d\n", student[now_real].namae, max_candy);
        //system("pause");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值