题意:一群小孩围坐成一圈,按顺时针方向编号为1~N;每个孩子手里拿着一张纸片,上边有一个非零整数x;
从第k个孩子开始淘汰,当某一孩子淘汰后,下一个淘汰的和他手上的纸片有关:
纸片上的数x大于0,淘汰他左边的第x个孩子,反之淘汰他右边的第x个孩子;
第i个淘汰的孩子会得到i的约数的个数块糖,即:若是第4个淘汰,4有1,2,4三个约数,那么就得到3块糖;
思路:
首先要知道第几个淘汰的分到的糖多,即:1~N那个数的约数多;
直接求每个数的约数,这里有个优化:
对每个数都求一遍约数显然复杂度太高,我们反过来想,看每个数是谁的约数;
最后就是要模拟这个特殊的约瑟夫环了;假设已经找到1~N约数最多的是P,怎样快速找到第P个淘汰的人呢?
我的想法是用线段树,其叶子就表示孩子1~N编号,只要能找到每次淘汰的是剩下的人中第几个就可以了;
假设共有4人,当前被淘汰的是第2个,手中的数字是3,那么下一个被淘汰的是剩下人中的第几个呢?
如上图去掉2后,左手边第3个是1,是剩下的人中第1个;
...
经过观察得:当x>0时,k=((k-1+x-1)%mod+mod)%mod+1;
当x<=0时,k=((k-1+x)%mod+mod)%mod+1;
其中k表示上一个被淘汰的是第几个,mod表示当前剩余人数;
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn = 500100;
int id, n, k;
int ans[maxn];
struct node{
int A;
char name[15];
}pers[maxn];
void init(){
memset(ans, 0, sizeof(ans));
for(int i=1; i<=n; i++){
ans[i]++;
for(int j=i+i; j<=n; j+=i){
ans[j]++;
}
}
id=1;
int m=ans[1];
for(int i=1; i<=n; i++){
if(m<ans[i]){
id=i;
m=ans[i];
}
}
}
struct Node{
int x, l, r;
}tree[maxn<<2];
void updown(int m){
tree[m].x=tree[m<<1].x+tree[m<<1|1].x;
return;
}
void build(int m, int l, int r){
tree[m].l=l;
tree[m].r=r;
if(l==r){
tree[m].x=1;
return;
}
int mid=(l+r)>>1;
build(m<<1, l, mid);
build(m<<1|1, mid+1, r);
updown(m);
}
int updata(int m, int v){
if(tree[m].r==tree[m].l){
tree[m].x=0;
return tree[m].l;
}
int temp;
if(tree[m<<1].x>=v) temp=updata(m<<1, v);
else temp=updata(m<<1|1, v-tree[m<<1].x);
updown(m);
return temp;
}
int main(){
while(~scanf("%d%d", &n, &k)){
for(int i=1; i<=n; i++){
scanf("%s%d", pers[i].name, &pers[i].A);
}
init();
build(1, 1, n);
int t=id;
pers[0].A=0;
int pos=0;
int mod=n;
while(t--){
if(pers[pos].A>0){
k=((k-1+pers[pos].A-1)%mod+mod)%mod+1;
}
else{
k=((k-1+pers[pos].A)%mod+mod)%mod+1;
}
pos=updata(1, k);
mod=tree[1].x;
}
printf("%s %d\n", pers[pos].name, ans[id]);
}
return 0;
}