POJ - 2886 Who Gets the Most Candies? 树状数组 + 二分 + 反素数

传送门:POJ2886

题意:n个小朋友在玩一种类似于约瑟夫环的游戏,定义F(p)表示p的约数个数,第p个出队的将会得到F(P)个糖,问哪个小朋友得到的糖最多。

思路:首先明确我们一开始就能知道第几个出队的小朋友能得到最多的糖果,并且这还是一类特殊的数,叫反素数,

定义:

对于任何正整数,其约数个数记为,例如,如果某个正整数满足:对任意的正整

            数,都有,那么称为反素数。

关于反素数详见:点击打开链接


有了反素数相关的知识,我们就可以先预处理出所有的数的因子,然后就能直接找到第几个出队的小朋友会得到最多的糖,下面解决如何得到这位小朋友的名字,模拟游戏的过程中最麻烦的地方在于删除操作,我们可以用一个树状数组来表示最初序列中的某一个位置有没有人(0 or  1),在模拟游戏的过程中,我们逐个计算下一个要出队的小朋友是当前序列的第几个,但是如何得知他在最初序列的位置呢,可以在树状数组上进行二分,因为树状数组本身就是前缀和数组,满足二分的单调性质,这样一来问题就解决了。

代码:

#include
#include
#include
#include
#define ll long long
#define pb push_back
#define fi first
#define se second
#define pi acos(-1)
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define rep(i,x,n) for(int i=x;i=x;i--)
using namespace std;
typedef pairP;
const int MAXN = 500010;
const int MAXM = 1010;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int prime[MAXN];
bool isprime[MAXM];
int num = 0;
P anti_prime[MAXM];
void dfs(int fac_num, int val, int pos, int limit)
{
	if(val > MAXN) return ;
	anti_prime[num++] = P(val, fac_num);
	int tmp = val;
	for(int i = 1; i <= limit && tmp < MAXN; i++)
	{
		tmp *= prime[pos];
		dfs(fac_num * (i + 1), tmp, pos + 1, i);
	}
}
void init()
{
	// get prime
	int cnt = 0;
	memset(isprime, 1, sizeof(prime));
	isprime[1] = 0;
	for(int i = 2; i < MAXM; i++)
	{
		if(isprime[i])
		{
			prime[cnt++] = i;
			for(int j = i + i; j < MAXM; j += i)
			isprime[j] = 0;
		}
	}
	// get antiprime
	num = 0;
	dfs(1, 1, 0, 20);
	sort(anti_prime, anti_prime + num);
}
int bit[MAXN];
int sum(int i)
{
	int res = 0;
	while(i)
	{
		res += bit[i];
		i -= -i & i;
	}
	return res;
}
void add(int i, int delta)
{
	while(i < MAXN)
	{
		bit[i] += delta;
		i += i & -i;
	}
}
int bi_search(int val)
{
	int l = 0, r = MAXN, mid;
	while(l <= r)
	{
		mid = (l + r) >> 1;
		if(sum(mid) >= val)
			r = mid - 1;
		else
			l = mid + 1;
	}
	return r + 1;
}
char name[MAXN][15];
int card[MAXN];
int main()
{
	int n, k;
	init();
	//cout << num << endl;
	//for(int i = 0; i < num; i++)
	//printf("%d %d\n", anti_prime[i].fi, anti_prime[i].se);
	while(cin >> n >> k)
	{
		memset(bit, 0, sizeof(bit));
		for(int i = 1; i <= n; i++)
		{
			scanf(" %s %d", name[i], card + i);
			add(i, 1);
		}
		int up = 0, ans = 0;
		for(int i = 0; i < num; i++)
		{
			if(anti_prime[i].fi > n) break;
			if(anti_prime[i].se > ans)
			up = anti_prime[i].fi, ans = anti_prime[i].se;
		}
		int now = k, nxt = k;
		for(int i = 1; i < up; i++)
		{
			add(now, -1);//将小朋友出队
			int m = n - i;
			int x = card[now];
			if(x > 0)//计算下一个要出队小朋友在当前序列的位置
			nxt = (nxt - 1 + x) % m;
			else
			nxt = ((nxt + x) % m + m) % m;
			if(nxt == 0) nxt = m;
			//printf("%d %s\n", now, name[now]);
			now = bi_search(nxt);//得到下一个要出队的小朋友在原序列的位置
			//printf("%d %d %d\n", now, nxt, m);
		}
		printf("%s %d\n", name[now], ans);
	}
 	return 0;
}


你可能感兴趣的:(poj,二分,数学)