XXR1T2题解
XXR1T2 题解
题面
思路
首先我们有引理:对于一件事情 \(X\),我们每一次做 \(X\) 的成功概率是 \(P\),如果成功则不继续做,不成功则继续做,这样做成功的期望次数是 \(\frac{1}{P}\),
证明:设做成功的期望次数为 \(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;
}

浙公网安备 33010602011771号