Codeforces 101864A 打表

传送门:题目

题意:

大概意思就是总共有N个犯人,形成一个序列[1-n],有一个犯人在第X位,然后警察们制定了一个规则,让[1-L?]犯人出队,围成一个约瑟夫环,警察们要杀N-1个人,只允许L人中存活一人,杀人规则是:
[1-L?]个犯人围城一个圈,然后留一人,杀一人。比如,下图:
L==8时,[1-8]围成一个圈

留下一号选手,击杀二号选手。
留下三号选手,击杀四号选手。
留下五号选手,击杀六号选手。
留下七号选手,击杀八号选手。
留下一号选手,击杀三号选手。
留下五号选手,击杀七号选手。
留下一号选手,击杀五号选手。
留下一号选手,咦?好像没人可以杀了。我们数一数,上面正好七行,正好满足题意,留下一个幸存者,恭喜一号选手获胜。

不难发现,这正是著名的约瑟夫环问题。而这正好是一个特殊的约瑟夫环:Josephus(n,2)。为什么Josephus(n,2)特殊,稍后再讲。我们继续说题意。

上面L打了个问号,因为题目的意思是,上面区间的右界是[L,N]的任何一个数。也就是说:

第一种情况:[1-L]出队,只允许存活一人
第二种情况:[1-L+1]出队,只允许存活一人
等等
第N-L+1种情况:[1-N]出队,只允许存活一人

没出队的肯定活着
好了,题目问:所有出队的情况,X号犯人,能存活的概率。
其中: 1X,L,N1015 1 ≤ X , L , N ≤ 10 15

题解:

所有出队的情况,N-L+1做分母
分子我们考虑两种情况,一种是X号犯人根本没出队,肯定存活。另一种就是本问题的核心了。如果没有那个神奇的 1015 10 15 ,我们直接写个Josephus(i,2)函数,返回幸存者编号,如果是X,分子++。显然,这道题没有这么简单。完全没有思路,先打个表再说:我们输出:i [1,100],Josephus(i,2)的返回值。

打表代码:
#include <iostream>
using namespace std;
int main(void){
    int m=2, ans;
    for(int i=1;i<=100;i++){
        ans=0;
        for(int j=2;j<=i;j++)
            ans=(ans+m)%j;//约瑟夫出圈公式
        cout<<ans+1<<" ";
    }
    cout<<endl;
    return 0;
}

得到下图结果:
运行结果
看着比较乱,我们不妨换一下行:

1
1 3
1 3 5 7
1 3 5 7 9 11 13 15
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
……

我们发现,每当Josephus(i,2)中,i为 2k,k[0,1,2,3,......] 2 k , k ∈ [ 0 , 1 , 2 , 3 , . . . . . . ] 时,Josephus(i,2)==1,然后开始奇数递增。
一个很神奇的事情发生了,比如说现在X==5,那么X在每一行只能存活1次,所以我们只需要计算[X,N]横跨了那几行,如果是完全横跨,分子++,如果一行中只有部分在[X,N]内,判断一下X值,是不是在区间内就行了,一个很巧妙的办法,直接把X/2,为何巧妙?

AC代码:

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long//作者比较懒,直接把所有int换成long long

void solve(int i)   {
    int X, L, N;
    scanf("%I64d%I64d%I64d", &X, &L, &N);
    int ans = max(X - L, 0ll);
    if (X & 1ll) //x是奇数
        for (int Y = 1 ; Y + X / 2 <= N ; Y <<= 1)    {//X是1,3,5,7
            if (Y + X / 2 < L || Y <= X / 2)//那么X/2就是0,1,2,3,直接就是X的在行的第几位了。
                continue;
            ++ans;
        }

    int P = ans, Q = N - L + 1;
    int g = __gcd(P, Q);//不要忘记化简分子和分母啊,__gcd是algorithm中的一个函数,很方便
    P /= g;
    Q /= g;
    cout << "Case " << i << ": " << P << "/" << Q << "\n";
}

signed main()   {//不能是long long main啊,所以我们就用signed吧,反正signed==int
    int T;
    scanf("%I64d", &T);
    for (int i = 1 ; i <= T ; ++i)
        solve(i);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值