您的位置:首页 > 其它

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

2014-08-09 22:32 281 查看
我昨天才知道模拟约瑟夫环是可以用线段树来解的……

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

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

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

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

以POJ 2886的样例为例:

绝对位置1 2 3 4
实际站的人12(+4)34
13(-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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: