POJ 2886 Who Gets the MostCandies?(线段树+模拟+求数的约数个数)
http://poj.org/problem?id=2886
题意:
n个孩子按顺时针排列,每个人手上都有一张牌,牌上有一个数字,从第k个孩子开始出队,出队的孩子卡上数字是val,则从他开始顺时针第val人是下一个出队的,负数则逆时针数那个第val个人,第P个出队的会得到的糖果数是p的因子个数,输出得到最多糖果的人和他的糖果数,如果有多个,则输出最先出队的人。
分析:
其实本题就是模拟每轮游戏,然后在每一轮游戏中先算出当前需要出队的第j个孩子,然后用线段树找到还在队中的第j个还是得原始编号。
首先建立一棵线段树,其每个叶节点都是1(代表每个孩子都在圈中,如果第i个孩子不在圈中,那么就令第i个叶子(孩子的编号不是i而是i管理的区间l或r)sum=0)。
分析题中的例子:
4 2
Tom 2
Jack 4
Mary -1
Sam 1
假设我们第一个出去的孩子是第2个孩子,那么我们把第二个叶节点的值置为0,该步只需要通过update找到sum值正好为2的那个区间的右端点(且该右端点的sum值也为1)即可。然后让这个sum变为0,表示这个孩子出了圈。并且我们读到了JACK的val值为4,当前圈剩余3个人,所以我们向后找4%3=1个人,如果[3,n]内的sum和>=1,那么直接在JACK右边即[3,n]区间找即可。如果sum的和<1,那么就在JACK左边[1,1]内找1-sum的那个位置的1.
依次类推即可。线段树的每个叶节点维护name,val,sum三个值,非叶节点只需要维护sum值即可。
AC代码:1672ms
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 500000+100;
#define lson i*2,l,m
#define rson i*2+1,m+1,r
int sum[MAXN*4];
int cnt;
struct node
{
char name[10];
int val;
}nodes[MAXN];
void PushUp(int i)
{
sum[i]=sum[i*2]+sum[i*2+1];
}
void build(int i,int l,int r)
{
if(l==r)
{
sum[i]=1;
return ;
}
int m=(l+r)/2;
build(lson);
build(rson);
PushUp(i);
}
int update(int q,int i,int l,int r)//找到第q个sum为1的叶节点,并返回其在线段树中的节点编号
{
if(l==r)
{
sum[i]=0;
return l;
}
int m=(r+l)/2;
int res;
if(q<=sum[2*i]) res= update(q,lson);
else res= update(q-sum[2*i],rson);
PushUp(i);
return res;
}
int f[500010];//f[i]=x表示i的约数有x个
void get_f()
{
int i,j;
for(i=1;i<=500000;i++)
for(j=1;i*j<=500000;j++)
{
f[i*j]++;//假设x=i*j,那么i*j和j*i时,f[x]都会++,正好是加上了i和j各一次。
}
}
int main()
{
int n,k;
memset(f,0,sizeof(f));
get_f();
while(scanf("%d%d",&n,&k)==2&&n&&k)
{
build(1,1,n);
for(int i=1;i<=n;i++)
{
scanf("%s%d",nodes[i].name,&nodes[i].val);
}
if(n==1)
{
printf("%s 1\n",nodes[1].name);
continue;
}
int j=k;
int ans=0;
char ans_name[10];
cnt=n;//cnt为当前有效叶节点的总数
for(int i=1;i<=n;i++)
{
//printf("j=%d\n",j);
int num=update(j,1,1,n);//num是指原始序列的第num个孩子,j是指线段树区间[1,n]内的第j个1
cnt--;
int x= f[i];
int v=nodes[num].val;
if(ans<x)
{
ans = x;
strcpy(ans_name,nodes[num].name);
}
if(i==n)
break;
int l_num=j-1;
int r_num=cnt-j+1;
if(v<0)//用v值来得到下一个需要处理的j值
{
v=(-v)%cnt;
if(v==0)v=cnt;
if(l_num>=v) j =l_num-v+1;
else j=cnt-(v-l_num)+1;
}
else if(v>0)
{
v=v%cnt;
if(v==0)v=cnt;
if(r_num>=v)j=l_num+v;
else j=v-r_num;
}
}
printf("%s %d\n",ans_name,ans);
}
return 0;
}