P13831 【MX-X18-T3】「FAOI-R6」比亚多西-题解
P13831 【MX-X18-T3】「FAOI-R6」比亚多西-题解
upd:优化证明,补充知识点。
看了官方解答后才发现自己的做法有多么【数据删除】,给大家分享一下。
简要题意
有一个区间
- 每次取中点
m=\lfloor (l+r)/2 \rfloor ,操作次数s 加一; - 如果
m=k ,过程结束; - 否则按二分方式更新区间,继续。
最终
对
即对所有
现在给你多个询问
题目分析
我们先考虑函数
我们可以用类似于线段树的方法建树,举个例子:
如果我们对区间
举个例子,当
-
实际上,这样的树被叫做二叉搜索树,对于二叉搜索树,它或者是一棵空树,或者是具有下列性质的二叉树:
-
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
-
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
-
它的左、右子树也分别为二叉排序树。
-
显然,这样构造出的树深度是
我们可以利用下面公式求出
::::info[证明]
对于第
最后一层
它们的深度都是
把各层贡献相加,就得到总深度和:
考虑化简下面这个式子:
我们可以考虑利用恒等式
你可以发现
考虑
因此:
代回到
:::: 其实这个东西打完暴力 oeis 可以搜到。
很显然的是
在区间
那么区间和就是:
其中
代码实现
二进制分组感觉实现细节比较多,需要开 __int128,洛谷可过,梦熊需要卡常。可能需要优雅地取模。
FUN FACT:赛时我在 MXOJ 上提交了近 1015ms。
#include <bits/stdc++.h>
using namespace std;
#define int __int128
inline int read()
{
int 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;
}
inline void print(int n)
{
if(n<0)
{
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
}
const int mod=998244353;
const int inv=499122177;
// 2 在 998244353 下的逆元
int f(int n)
{
int ans=0;
for(int i=1; i<=61; i++) // 枚举 h 的取值范围(因为 n ≤ 2^61),区间 [l, r] 上 h 恒等于 i
{
int l=(1ll<<(i-1)); // 区间左端点 l = 2^{i-1}
if(l>n) break; // 如果左端点已经超过 n,后面区间都无效
int r=(1ll<<i)-1; // 区间右端点 r = 2^i - 1
if(n<r) r=n; // 被 n 截断,只取到 n
int len=(r-l+1)%mod;
l%=mod;
r%=mod;
int sum=(l+r)*len*inv%mod;
ans=(ans+i*sum-len*(1ll<<i)%mod+len*(i+1)+mod)%mod;
}
return ans;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=read();
while(T--)
{
int L=read(),R=read();
print((f(R)-f(L-1)+mod)%mod);
putchar('\n');
}
return 0;
}