关于反素数:摘自此博客:http://magicode.blog.sohu.com/120450550.html
这个题主要用到线段树的思想,每次推算出要出去的人在当前剩下的人中的排位,再用线段树求出其原来的编号,即可算出每次应该出去的人,该人得到的糖果数为f(p)(p为出去的顺序,f(p)为p约数的个数
其实当总人数n确定时,p的值和f(p)的值就确定了,p为小于等于n的最大反素数
新学一个反素数的概念
反素数n,1<=i<n,则f(i)<f(n),即在1-n中,n的约数最多
由于反素数个数不多,这个题可以打表,把1-500000之间的反素数先算出来
反素数有以下两个性质
性质一:一个反素数的质因子必然是从2开始连续的质数.
性质二:p=2^t1*3^t2*5^t3*7^t4.....必然t1>=t2>=t3>=....
这两个性质都比较好证明
所以可以用递归的方法把反素数求出来
代码:
//反素数
#include <iostream>
#include <cmath>
using namespace std;
//32位整数的反素数只能由这几个素数构成
int prime[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
int bestNum; //反素数
__int64 bestSum;//反素数的约数个数
//当数为num时,其约数个数为sum,本次对第k个素数的幂进行选取,
//幂最高只能选limit, 求比n小的最大的反素数
void search(int num, __int64 sum, int k, int limit, int n)
{
if (k >= 9) return;
if (sum > bestSum) { //更新
bestSum = sum;
bestNum = num;
}
else if (bestSum == sum && bestNum > num) //约数个数相同时,选取较小的数
bestNum = num;
int i, newNum = num;
for (i = 1; i <= limit; i++) //第k个素数取i次方
{
newNum *= prime[k];
if (newNum > n) break;
search(newNum, sum * (i + 1), k + 1, i, n); //第k+1个素数最多取i次,根据上面的第二条性质
}
}
int main()
{
int n = 500000;
cout << (int)log2(n * 1.0) << endl;
int i = 0;
while (n > 0)
{
i++;
bestNum = 1; bestSum = 1;
search(1, 1, 0, (int)log2(n * 1.0), n);
cout << "{" << bestNum << ", " << bestSum << "}, ";
n = bestNum - 1;
}
cout << i << endl;
return 0;
}
题意:输出第p个退出环的人的编号,使得p的约数最多。
解题:p的值为小于n的最大的反素数,因为此时p的约数最多。
#include <iostream>
#include <cstdio>
using namespace std;
#define N 500005
//反素数表
int antiprime[35][2] = {
{498960, 200}, {332640, 192}, {277200, 180}, {221760, 168}, {166320, 160}, {110880, 144},
{83160, 128}, {55440, 120}, {50400, 108}, {45360, 100}, {27720, 96}, {25200, 90},
{20160, 84}, {15120, 80}, {10080, 72}, {7560, 64}, {5040, 60}, {2520, 48}, {1680, 40},
{1260, 36}, {840, 32}, {720, 30}, {360, 24}, {240, 20}, {180, 18}, {120, 16}, {60, 12},
{48, 10}, {36, 9}, {24, 8}, {12, 6}, {6, 4}, {4, 3}, {2, 2}, {1, 1} };
int sum[N << 2], num[N]; //sum数组存储区间内的人数
char name[N][12];
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] = 1; return;
}
int m = (l + r) >> 1;
build (l, m, rt << 1);
build (m + 1, r, rt << 1 | 1);
pushUp(rt);
}
void update(int p, int l, int r, int rt)
{
if (l == r) {
sum[rt] = 0; return;
}
int m = (l + r) >> 1;
if (m >= p) update(p, l, m, rt << 1);
else update(p, m + 1, r, rt << 1 | 1);
pushUp(rt);
}
int query(int k, int l, int r, int rt)
{
if (l == r) return l;
int m = (l + r) >> 1;
if (sum[rt << 1] >= k) return query(k, l, m, rt << 1);
else return query(k - sum[rt << 1], m + 1, r, rt << 1 | 1);
}
int main()
{
int i, n, k, pos, p, candy;
while (scanf ("%d %d", &n, &k) != EOF)
{
i = 0;
while (n < antiprime[i][0]) i++;
p = antiprime[i][0];
candy = antiprime[i][1];
for (i = 1; i <= n; i++)
scanf ("\n%s %d", name[i], &num[i]);
build (1, n, 1);
i = 1; pos = k; //k为人在约瑟夫环中的相对位置,pos为该人在最初的环中的绝对位置
while (i < p) {
update (pos, 1, n, 1); //删除当前这个人
//求出下一个人的在约瑟夫环中的相对位置
k += num[pos];
if (num[pos] > 0) k--; //注意:当num[pos] > 0时,pos后面的人的相对位置都要减1
k = (k % sum[1] + sum[1]) % sum[1]; //sum[1]为剩余人的人数
if (k == 0) k = sum[1];
//由相对位置求出绝对位置
pos = query(k, 1, n, 1);
i++;
}
printf ("%s %d\n", name[pos], candy);
}
return 0;
}