題意:一堆小朋友圍成一個圈,規定從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\n",e[pos].name,MAX);
}
return 0;
}