题意:有n个小孩围成一圈,编号从1到n,每个人手上都有一个纸片,上面数字是num[i],开始第k个人离开,然后按第k个人纸片上的数字num[k],如果是正数,下一个离开的是左边第num[k]个人,负数是右边第-num[k]个人离开,游戏一直持续到最后一个人离开。第p个出去的人都会得到p的约数个数的糖果,问谁得到的糖果数最多,输出人名和约数个数。
题解:开始不知道F(p)是什么,后来查了下才知道是反素数,打了素数表后,知道了n就知道第几个出去的得到的糖果最多,那么接下来就是求得到糖果数量最多的人的名字。用线段树维护每个区间的人数,然后用约瑟夫的递推公式f[i] = (f[i - 1] + m) % i可以得到下一个离开的人的序号。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500005;
int antip[] = {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};
int pnum[] = {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};
int sum[N << 2];
char name[N][15];
int num[N];
void pushup(int k) {
sum[k] = sum[k * 2] + sum[k * 2 + 1];
}
void build(int k, int left, int right) {
if (left == right) {
sum[k] = 1;
return;
}
int mid = (left + right) / 2;
build(k * 2, left, mid);
build(k * 2 + 1, mid + 1, right);
pushup(k);
}
int modify(int k, int left, int right, int p) {
if (left == right) {
sum[k] = 0;
return left;
}
int mid = (left + right) / 2, temp;
if (p <= sum[k * 2])
temp = modify(k * 2, left, mid, p);
else
temp = modify(k * 2 + 1, mid + 1, right, p - sum[k * 2]);
pushup(k);
return temp;
}
int main() {
int n, k;
while (scanf("%d%d", &n, &k) == 2) {
int m = 0;
while (antip[m] <= n)
m++;
int res = pnum[m - 1];
m = antip[m - 1];
for (int i = 1; i <= n; i++)
scanf("%s%d", name[i], &num[i]);
build(1, 1, n);
int cur = k, cnt = n;
while (m--) {
k = modify(1, 1, n, cur);
cnt--;
if (!m)
break;
if (num[k] > 0)
cur = (cur - 1 + num[k] - 1) % cnt + 1;
else
cur = ((cur - 1 + num[k]) % cnt + cnt) % cnt + 1;
}
printf("%s %d\n", name[k], res);
}
return 0;
}