jzoj 4026. 【佛山市选2015】约瑟夫问题

Description

现有N*(N+1)/2 个人围成一圈,编号从1到N*(N+1)/2,其中编号i与编号i+1的人相邻,编号N*(N+1)/2与编号1相邻。如今,我们要执行N-1个阶段的杀人仪式,在第一个阶段开始前,从编号为1的人开始报数。在第i阶段中,报到n+1-i这个数的人要被杀害,之后从被杀害者的下一个相邻的人开始从1开始报数,在第i个阶段里需要杀死n+1-i个人,随后即可进入下一个阶段,更具体地说:
在第1个阶段中,报数为N的人会被杀害,在这个阶段中需要杀死N个人;
在第2个阶段中,报数为N-1的人会被杀害,在这个阶段中需要杀死N-1个人;
.
.
.
在第n-1个阶段中,报数为2的人会被杀害,在这个阶段中需要杀死2个人。
仪式结束后,会剩下一个幸存者,请问这位幸存者的编号是多少?

Input

第一行一个整数T,表示数据的组数。
接下来T行,每行一个整数N,N定义如题目所述。

Output

输出一个整数,表示幸存者的编号

Sample Input

3
1
2
3

Sample Output

1
3
2

Data Constraint

20%数据,1<=n<=100,T <=10
40%数据,1<=n<=100,000,T<=10
100%数据,1<=n<=5,000,000,T<=100000

Solution

好难的找规律题啊,ヾ(。ꏿ﹏ꏿ)ノ゙,不看题解只有暴力想法。。。
首先我们可以发现,当把第一阶段的数删了之后,原来的 n(n+1)2 n ∗ ( n + 1 ) 2 变成了 n(n1)2 n ∗ ( n − 1 ) 2 个,而且,后面删除的和n-1时相似,就可以利用n-1时的结论了。
比如说(来自题解

我们设f[i]表示当i=n时,存活的人的编号是f[i]
f[4] = 10
现在要求f[5]
我们将他们列出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
第一轮死掉的人是5 , 10 , 15 , 6 , 12
剩下的数是
1 2 3 4 7 8 9 11 13 14
删掉12之后我们就是从13开始的
即现在的数列是
13 14 1 2 3 4 7 8 9 11
那么这一个数列的第10个数就是f[5]即11

所以,要知道最后一个在第一阶段删的数,即那些数已经删掉(计算时跳过)
可以列几个数找出删掉数的规律

1.当n为偶数时,删的数依次为
  n,2n,3n,....(n/2)*n
  n/2,n/2+(n+1),n/2+2(n+1),....n/2+(n/2-1)*(n+1)
2.当n为奇数时,删的数依次为
  n,2n,3n,....(n+1)/2*n
  n+1,2(n+1),....(n-1)/2*(n+1)

然后,往后走f[n-1]个数(跳过被删掉的),注意有可能从n*(n+1)/2走到1,而且要开longlong记录。
蒟蒻写的比较慢(而且很丑[捂脸]),超时了,且不会卡常,只能开o2卡时间了。。。T_T


#pragma GCC opitmize("O3")
#pragma G++ opitmize("O3")
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

#define N 5000050
#define M 5000000
#define LL long long

LL f[N];
int ti,n;

LL min(LL x,LL y)
{
    if (x<y) return x;
      else return y;
}

__attribute__((optimize("-O3")))
LL js(LL l,LL r,LL t)
{
    LL sl,sr;
    if (t%2==0)
    {
        sl=l/t+(l-t/2+t+1)/(t+1);
        sr=r/t+(r-t/2+t+1)/(t+1);
    }
    else
    {
        sl=l/t+l/(t+1);
        sr=r/t+r/(t+1);
    }
    return sr-sl;
}

__attribute__((optimize("-O3")))        
void pre()
{
    f[1]=1;
    LL x,y,z,w;
    LL last;
    for (LL i=2;i<=M;i++)
    {
      if (i%2==0)
      {
        z=i/2+(i/2-1)*(i+1);
        x=i/2*i; y=i/2;
      }
      else
        {
            z=i/2*(i+1);
        x=(i+1)/2*i; y=i+1;
      }     
      last=f[i-1];
      if (x-z>last)
      {
        f[i]=z+last;
        continue;
      }
      last+=1;
      if (i*(i+1)/2-z>=last)
      {
        f[i]=z+last;
        continue;
      }
      last-=i*(i+1)/2-z;
      z=0;
      while (js(z,z+last,i)>0)
      {
        w=z+last;
        last=js(z,z+last,i);
        z=w;
      }
      f[i]=z+last;
    }
}

int main()
{
    pre();
    scanf("%d",&ti);
    for (int ii=1;ii<=ti;ii++)
    {
        scanf("%d",&n);
        printf("%lld\n",f[n]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值