题目:POJ - 2886
题意:有N个人参加一个游戏,每个人手上都拿了一张卡片,每张卡片的数字都是非0的,现在从第K人开始,第K人离开后,这时候根据第K人手上拿着的卡片的数字,决定下一个人是谁,如果这个数字m为正数,那么其实相当于向右数m个,如果这个数字为负数,那么其实相当于向左数m个数的位置(所有的数法都是要排除自己)。
题解:
1.由于i的约数的个数是可以求出的,也就是说只需要在N内找到一个约束最大的数X,判断谁是第X个退出的就可以了。这里就用到了反素数的概念,设G[i]表示i的约数的个数,若对于任意的j,j<i有G[i] > G[j],那么i就是i反素数,这道题就转化为了球N以内的最大的反素数,设Max[i]为i以内的最大的反素数是Max[i]。那么我就可以对于每一个n预处理出他的反素数X,求出第X个退出的人。
2.那么对于走掉的人我们进行删除,剩下的人类似于POJ-2828的空位,对于当前那个人手中的卡片上的数值a[i],那么我要找的就是从他之后(或之前)的a[i]位,也就是代码中对应的第k个空位,第k个空位对应第pos个人
3.关于k的求法:如果这个数是正数y,那么当前位置K前面的数有K-1个,即前面有K-1个数,这时候为防止取模后等于0,先减去1,最后取模完成后再加上1,这时候加上y,就是下一个数的位置(注意这里说的位置说的是第几个空位)负数我们可以这样思考,因为正数相当于向前走,那么负数就是说要倒着数,和正数一样,先减去1,然后取模再加上1。(还是有点蒙蔽,但是一般碰到求约瑟夫环问题都可以这么求了),碰到出队的人数的数为m,数的数对应于a[pos],k对应的空位的位置
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
#define N 500010
#define lson node<<1
#define rson node<<1|1
using namespace std;
struct ljh
{
int l,r,sum;
}e[N<<2];
int n,k,id,m,pos;
int c[N],a[N];
char s[N][15];
void init()
{
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){//以i为因子
for(int j=i;j<=n;j+=i)
c[j]++;//j以i为因子,j的因数++
}//反素数法
int tmp=0;
id=0;
for(int i=1;i<=n;i++)
if(c[i]>tmp){
id=i;
tmp=c[i];
}
}
void pushup(int node)
{
e[node].sum=e[lson].sum+e[rson].sum;
}
void build(int node,int l,int r)
{
e[node].l=l;
e[node].r=r;
e[node].sum=0;
if(l==r)
{
e[node].sum=1;
return ;
}
int m=(l+r)>>1;
build(lson,l,m);
build(rson,m+1,r);
pushup(node);
}
int update(int node,int z)
{
e[node].sum--;
if(e[node].l==e[node].r)
{
// e[node].sum=0;
return e[node].l;
}
if(e[lson].sum>=z)return update(lson,z);
else return update(rson,z-e[lson].sum);//注意是减去左子树的个数
// pushup(node);
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
init();
for(int i=1;i<=n;i++)
scanf("%s%d",s[i],&a[i]);
build(1,1,n);
int m=n;
n=id;
pos=0;
a[0]=0;
while(n--)
{
if(a[pos]>0)
k=(k-1+a[pos]-1)%m+1;//第k个空位
else
k=((k-1+a[pos])%m+m)%m+1;
// cout<<"K="<<k<<endl;
pos=update(1,k);//第k个空位对应的位置
// cout<<pos<<endl;
m=e[1].sum;
}
printf("%s %d\n",s[pos],c[id]);
}
return 0;
}