题意:一堆小朋友围成一个圈,规定从k开始玩,每个被选中的人都有一个数字,正数代表从他左边开始数num,负数从右边数,被选中的人继续按照上述操作,直到都退出圈子,第i个退圈的人能拿到一个点数,这个点数是i的因数个数(比如第4个人拿3点)。问:谁点数最多,一样多选第一个。
思路:一道好题啊,万万没想到是线段树,想要想到感觉还是有点难度的。这道题其实就是要一直维护剩余人数,但是以前没做过线段树维护剩余人数的,一时半会想不到...首先,我们要用打表找到所有点数。然后用线段树维护剩余人数,这里有一个剩余人数位置和所有人数位置转换的过程,是用线段树解决的。线段树的每个节点储存的是某个区间的剩余人数,所以我们在用一个人在剩余人中的位置找到他的编号时,是比较sum节点而非L,R。还有向左向右方向要注意,画个圈慢慢感受。具体看注释。
代码:
#include<cstdio>
#include<vector>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define ll long long
const int maxn = 500000+5;
const int MOD = 1e7;
const int INF = 0x3f3f3f3f;
using namespace std;
struct node{
char name[12];
int num;
}e[maxn];
int get[maxn],sum[maxn << 2];
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);
}
int update(int pos,int l,int r,int rt){
if(l == r){
sum[rt] = 0;
return l;
}
int ans;
int m = (l + r) >> 1;
if(pos <= sum[rt << 1]) //pos代表在剩余人数中的第几个,所以写法略有不同
ans = update(pos,l,m,rt << 1);
else
ans = update(pos - sum[rt << 1],m + 1,r,rt << 1 | 1);
pushup(rt);
return ans;
}
void init(){
int top = 500000;
memset(get,0,sizeof(get));
for(int i = 1;i <= top;i++){
get[i]++;
for(int j = 2*i;j <= top;j += i){
get[j]++;
}
}
}
int main(){
init();
int n,k;
while(~scanf("%d%d",&n,&k)){
build(1,n,1);
for(int i = 1;i <= n;i++)
scanf("%s%d",e[i].name,&e[i].num);
int aim = 0,MAX = -1;
for(int i = 1;i <= n;i++){
if(get[i] > MAX){
aim = i;
MAX = get[i];
}
}
int pos; //开始计算的位置
int mov,all,now = 0;
while(true){
pos = update(k,1,n,1); //被宰的是哪个人
now++;
if(now == aim) break;
mov = e[pos].num;
if(mov > 0){ //在左边
k = (k - 1 + mov - 1)%sum[1] + 1; //这里的pos是剩余人中的编号
//第一个-1是因为pos这个人已经被宰了,所以他这个位置其实是没有的
//第二个-1是防止%之后归0,所以先-1再+1
}
else{ //在右边
k = ((k - 1 + mov)%sum[1] + sum[1])%sum[1] + 1;
}
}
printf("%s %d
",e[pos].name,MAX);
}
return 0;
}