XXR1T2题解

XXR1T2 题解

题面

题面传送门

原题传送门

真正的原题1

真正的原题2

思路

首先我们有引理:对于一件事情 \(X\),我们每一次做 \(X\) 的成功概率是 \(P\),如果成功则不继续做,不成功则继续做,这样做成功的期望次数是 \(\frac{1}{P}\)

证明:设做成功的期望次数为 \(num\),则有如下式子:

\[num=1+P\times0+(1-P)\times num \]

\(1\) 代表当前做了 \(1\) 次;\(P\times0\) 代表成功了则不继续做;\((1-P)\times num\) 代表没成功则继续做,期望次数为 \(num\)

移项得 \(num=\frac{1}{P}\)

那么开始解释这题。

\(0\) 只猫的猫舍直接判掉就好了。

每个猫舍独立,所以只要会一个就会全部,假设当前猫舍有 \(n(n\geqslant1)\) 只猫。

首先 \(n\) 只猫中取到 \(1\) 个没有被抓过的猫的概率为 \(\frac{n}{n}=1\),期望次数为 \(\frac{1}{1}=1\)

\(n\) 只猫中抓到剩下 \(n-1\) 只没有被抓过的猫中的一只的概率 \(P_2\)\(\frac{n-1}{n}\),期望次数 \(num_2\)\(\frac{1}{P_2}=\frac{n}{n-1}\)

……

最后 \(n\) 只猫抓到剩下唯一一只没有被抓过的猫的概率 \(P_n\)\(\frac{1}{n}\),期望次数 \(num_n\)\(\frac{1}{P_n}=n\)

所以这个猫舍的答案为 \(\begin{aligned}\sum_{i=1}^nnum_i=\sum_{i=1}^n\frac{n}{i}=n\times\sum_{i=1}^n\frac{1}{i}\end{aligned}\)

所以只要线性求逆元后加一个前缀和,最后就可以得出每一个猫舍的答案,加起来输出就好了。

于是就可以得到一个 \(80\) 分的做法:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll __int128
using namespace std;
const int MN=1e6+5;
const ll mod=1145141999;
ll n,a[MN],inv[MN],sum[MN],ans;
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
int main(){
    freopen("cat.in","r",stdin);
    freopen("cat.out","w",stdout);
    n=read();
    for(int i=1; i<=n; i++) a[i]=read();
    sum[1]=inv[1]=1;for(int i=2; i<MN; i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod,sum[i]=(sum[i-1]+inv[i])%mod;
    for(int i=1; i<=n; i++) ans=(ans+a[i]*sum[a[i]]%mod)%mod;
    write(ans);
    return 0;
}//250216

接下来发现时间有 2s,所以可以有两只 log,于是把加减乘除都用位运算来实现了!

加法

就是在二进制下加,先计算没有进位的 \(a\bigoplus b\),然后将有进位的左移一位,以便后面加,而至于为啥代码中是 (a^b)&b,因为前面 \(a\) 已经异或过 \(b\) 了。

ll add(ll a, ll b){while(b!=0)a^=b,b=((a^b)&b)<<1;return a%mod;}

减法

减法可以通过加上负数的补码来实现。计算补码可以通过取反加一的方式来实现。

ll jian(ll a, ll b){return add(a,add(~b,1));}

乘法

正常龟速乘,就是和快速幂同样原理。

ll mul(ll a, ll b){
    ll res=0;
    if(a<b)swap(a,b);
    while(b){
        if(b&1)res=add(res,a);
        a<<=1;b>>=1;
    }
    return res%mod;
}

除法

除法可以通过减法和位移来实现。从被除数中减去除数的倍数,直到结果小于除数为止。每次减去的倍数可以通过左移除数来得到。


ll chu(ll a, ll b){
    ll res=0;
    while(a>=b){
        ll tmp=b,x=1;
        while(a>=(tmp<<1)){tmp<<=1;x<<=1;}
        a=jian(a,tmp);res=add(res,x);
    }
    return res;
}

于是,改一下代码就出来了,注意常数。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=1000005;
const ll mod=1145141999;
ll n,a[MN],inv[MN],sum[MN],ans,maxn;
ll add(ll a, ll b){while(b!=0)a^=b,b=((a^b)&b)<<1;return a%mod;}
ll mul(ll a, ll b){ll res=0;if(a<b)swap(a,b);while(b){if(b&1)res=add(res,a);a<<=1;b>>=1;}return res%mod;}
ll jian(ll a, ll b){return add(a,add(~b,1));}
ll chu(ll a, ll b){ll res=0;while(a>=b){ll tmp=b,x=1;while(a>=(tmp<<1)){tmp<<=1;x<<=1;}a=jian(a,tmp);res=add(res,x);}return res;}
int main(){
    freopen("cat.in","r",stdin);
    freopen("cat.out","w",stdout);
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1; i<=n; i=(i&1?add(i,1):i^1)) cin>>a[i],maxn=max(maxn,a[i]);
    sum[1]=inv[1]=1;for(int i=2; i<=maxn; i=(i&1?add(i,1):i^1)) inv[i]=mul(jian(mod,chu(mod,i)),inv[mod%i]),sum[i]=add(sum[jian(i,1)],inv[i]);
    for(int i=1; i<=n; i=(i&1?add(i,1):i^1)) ans=add(ans,mul(a[i],sum[a[i]]));
    cout<<ans;
    return 0;
}
posted @ 2025-02-16 23:55  naroto2022  阅读(74)  评论(0)    收藏  举报