POJ2886 Who Gets the Most Candies?(树状数组+二分+素数)
类似于约瑟夫环的一道题目。关键在于如何快速的求出下一个即将出队的孩子的编号。普通的约瑟夫环问题可以去模拟,但是数据量大不行。所以需要直接求出。
下一个即将出队的孩子为+A时,其在剩余孩子中的位置如下:
k
k
=
(
k
+
n
e
x
t
[
p
o
s
]
−
2
)
%
m
o
d
+
1
\ kk = (k + next[pos] -2)\% mod + 1\,
kk=(k+next[pos]−2)%mod+1
其中mod是此时还剩下的孩子的数量,k代表当前位置的孩子是剩下的孩子中的第k个,如果下一个出队的孩子是当前孩子左边的第A个时,直接让k加上A减去1就好了,但是为了防止余数出现0的情况,需要先减去一个1,最后再加上1。
下一个即将出队的孩子为-A时
k
k
=
(
(
k
+
n
e
x
t
[
p
o
s
]
−
1
)
%
m
o
d
+
m
o
d
)
%
m
o
d
+
1
\ kk = ((k + next[pos] -1)\% mod + mod)\% mod + 1\,
kk=((k+next[pos]−1)%mod+mod)%mod+1
这个是为了防止出现负数所以多加了个mod,其实还挺绕的。。
画个图看看。
然后我们既然知道了下一个即将出队的孩子在剩下的孩子中位置。
那么我就需要找到其具体的编号,怎么做呢,就是先用树状数组维护
BIT[i]代表前i个孩子中剩下的个数,那么我们就可以利用之前求出来的位置,去搜索具体的i,也就是如果BIT[j] == kk,那么孩子的位置一定位于位置j或者小于j。
为了更快速的找到孩子的位置,我们可以根据树状数组的单调性,利用二分查找去找。
还有就是约数的个数事先打表。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int max_n = 5e5;
int num[max_n + 10], prime[max_n + 10];
int bit[max_n + 10];
char name[max_n + 10][20];
int n, k;
inline int lowbit(int i) {return i & -i;}
void add(int i, int x) {
while (i <= n) {
bit[i] += x;
i += i & -i;
}
}
int sum(int i) {
int s = 0;
while (i > 0) {
s += bit[i];
i -= i & -i;
}
return s;
}
int binary_search(int x) {
// 二分找位置。
int l = 1, r = n, ans;
while (l <= r) {
int mid = l+(r-l)/2;
int s = sum(mid);
if (s < x)
l = mid + 1;
else {
r = mid - 1;
ans = mid;
}
}
return ans;
}
void getPrime() {
// 得到每个数约数的个数。
for (int i = 1; i<= max_n; i++) {
for (int j = i; j <= max_n; j += i) {
prime[j]++;
}
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%s%d", name[i], &num[i]);
add(i, 1);
}
getPrime();
int now = k, rem = n;
int res = prime[1], pos = now;
for (int i = 2; i <= n; i++) {
add(now, -1); //对于出队的孩子的位置置成-1。
rem--;
int kk = num[now];
if (kk > 0) k = (k + kk - 2)%rem + 1;
else k = ((k + kk - 1)%rem + rem)%rem + 1;
now = binary_search(k);
if (prime[i] > res) {
res = prime[i];
pos = now;
}
}
printf("%s %d\n", name[pos], res);
return 0;
}