POJ - 2886 - Who Gets the Most Candies? (线段树+反素数+找空位)

题目:POJ - 2886

题意:有N个人参加一个游戏,每个人手上都拿了一张卡片,每张卡片的数字都是非0的,现在从第K人开始,第K人离开后,这时候根据第K人手上拿着的卡片的数字,决定下一个人是谁,如果这个数字m为正数,那么其实相当于向右数m个,如果这个数字为负数,那么其实相当于向左数m个数的位置(所有的数法都是要排除自己)。

题解:

1.由于i的约数的个数是可以求出的,也就是说只需要在N内找到一个约束最大的数X,判断谁是第X个退出的就可以了。这里就用到了反素数的概念,设G[i]表示i的约数的个数,若对于任意的j,j<i有G[i] > G[j],那么i就是i反素数,这道题就转化为了球N以内的最大的反素数,设Max[i]为i以内的最大的反素数是Max[i]。那么我就可以对于每一个n预处理出他的反素数X,求出第X个退出的人。

2.那么对于走掉的人我们进行删除,剩下的人类似于POJ-2828的空位,对于当前那个人手中的卡片上的数值a[i],那么我要找的就是从他之后(或之前)的a[i]位,也就是代码中对应的第k个空位,第k个空位对应第pos个人

3.关于k的求法:如果这个数是正数y,那么当前位置K前面的数有K-1个,即前面有K-1个数,这时候为防止取模后等于0,先减去1,最后取模完成后再加上1,这时候加上y,就是下一个数的位置(注意这里说的位置说的是第几个空位)负数我们可以这样思考,因为正数相当于向前走,那么负数就是说要倒着数,和正数一样,先减去1,然后取模再加上1。(还是有点蒙蔽,但是一般碰到求约瑟夫环问题都可以这么求了),碰到出队的人数的数为m,数的数对应于a[pos],k对应的空位的位置

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
#define N 500010
#define lson node<<1
#define rson node<<1|1
using namespace std;
struct ljh
{
	int l,r,sum;
}e[N<<2];
int n,k,id,m,pos;
int c[N],a[N];
char s[N][15];
void init()
{
	memset(c,0,sizeof(c));
	for(int i=1;i<=n;i++){//以i为因子
		for(int j=i;j<=n;j+=i)
			c[j]++;//j以i为因子,j的因数++
	}//反素数法
	int tmp=0;
	id=0;
	for(int i=1;i<=n;i++)
		if(c[i]>tmp){
			id=i;
			tmp=c[i];
		}
}
void pushup(int node)
{
	e[node].sum=e[lson].sum+e[rson].sum;
}
void build(int node,int l,int r)
{
	e[node].l=l;
	e[node].r=r;
	e[node].sum=0;
	if(l==r)
	{
		e[node].sum=1;
		return ;
	}
	int m=(l+r)>>1;
	build(lson,l,m);
	build(rson,m+1,r);
	pushup(node);
}
int update(int node,int z)
{
	e[node].sum--;
	if(e[node].l==e[node].r)
	{
		// e[node].sum=0;
		return e[node].l;
	}
	if(e[lson].sum>=z)return update(lson,z);
	else return update(rson,z-e[lson].sum);//注意是减去左子树的个数
	// pushup(node);
}
int main()
{
	while(~scanf("%d%d",&n,&k))
	{
		init();
		for(int i=1;i<=n;i++)
			scanf("%s%d",s[i],&a[i]);
		build(1,1,n);
		int m=n;
		n=id;
		pos=0;
		a[0]=0;
		while(n--)
		{
			if(a[pos]>0)
				k=(k-1+a[pos]-1)%m+1;//第k个空位
			else
				k=((k-1+a[pos])%m+m)%m+1;
			// cout<<"K="<<k<<endl;
			pos=update(1,k);//第k个空位对应的位置
			// cout<<pos<<endl;
			m=e[1].sum;
		}
		printf("%s %d\n",s[pos],c[id]);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值