单组O(logn)解法
2025-04-28 02:46:30
发布于:浙江
下文我们使用 来表示美丽度,即二进制表示后末尾连续 的个数。
首先这里存在一个不易观察到的结论:
在题目的要求下, 的答案,即 ,实际上只与 相关。关于这部分在末尾会给出初等证明。
因此题目要求的东西,实际上和 是无关的,我们只需要求若干个 就可以了。
题目中所说的前 个正奇数,对应的就是 。任意奇数都可以表示成 的形式,我们取两个奇数 和 ,分别表示为 和 。
显然 ,我们尝试表示 :
第二步转化是显然的,一个数字乘以二等价于左移一位,所以末尾会多一个 。
那么我们记最后的答案为 ,则有:
我们可以发现 这个东西实际上是具有对称性的,因此上面这个式子可以进一步化简成:
是好算的,后面这一部分看起来并不好算,我们只要想出来怎么计算后面这一式子就可以了。
我们思考一下,对于若干个满足条件的 实际上我们在求贡献的时候,可以把其看做 ,其中 是一个奇数。我们不妨尝试计算对于每一个 有多少满足条件的 。
这里要记住,我们的 和 表达的实际上是我们对于前 个正整数编号之后, 和 就表示了他们是这个正奇数数列中的第几项。
接下来这部分很重要:
我们枚举所有的 ,定义 为其最大序号差值,即 。
这样一来我们可以使用 除以 ,来表示 中,我们最大可以取到的 。
因为我们上面说了 一定是一个奇数,我们定义 所以这里我们可以使用 来计算出 能取到多少个奇数,我们记其为 ,这个计算是平凡的,这里就不证明了。
之后我们记 ,这个的平方就是前 个奇数的和,这个的证明也是平凡的,可以使用等差数列的求和公式。
于是我们可以用 乘以 来计算出所有 的和。
对于每一个 ,它对应的 在 这个区间里出现的次数有 次,其证明就是我们通过固定 ,则 一定在 的区间里。
于是我们可以合理地计算出贡献了:
我们对最右边的式子进行拆分求和,就变成:
推到这个式子,已经就结束了,我们可以在 的复杂度内求解单组解。
最后计算答案的时候,总三元组个数有 个,所以我们有:
最后给出文章一开始提到的结论的初等证明:
首先 与 是奇数,因此 一定是一个偶数,我们将其表示为 。
我们对 做一个分解:
我们记其第二个因子为 ,因为 与 均为奇数,奇数的次幂一定是奇数,因此 中的每一项都是奇数,共有奇数项,因此 一定是一个奇数。
于是我们就可以进行如下的拆解:
因为 与 为奇数是肯定的,因此 与且仅与 相关,也就是与 无关,只和 相关。
文章开头的结论得证。
虽然这个题的 与 等阶,我这种做法和 预处理之后 求解单组的时间复杂度一样,但是单组 可以求解更大的 。
#define int long long
int fastPow(int a,int n) {
int ans = 1; a %= MOD;
while (n) {
if (n & 1) ans = (ans * a) % MOD;
a = (a * a) % MOD;
n >>= 1;
}
return ans;
}
void NeverSayNever() {
int n; cin >> n;
int m = n - 1;
int sum = 0;
for (int k = 1;; ++k) {
int kpow2 = 1LL << k;
if (kpow2 > m) break;
int q = m / kpow2;
if (q == 0) break;
int cntK = q - (q / 2);
int s = (q + 1) / 2;
sum += k * (cntK * (m + 1) - kpow2 * s * s);
}
int inv = fastPow(n * (n - 1) % MOD, MOD - 2);
int res = (1 + (((2 * (sum % MOD)) % MOD) * inv) % MOD) % MOD;
cout << res << endl;
}
全部评论 3
%%%佬
2025-04-29 来自 广东
0哥哥好厉害啊
2025-04-28 来自 浙江
0数学领域大🐍
2025-04-27 来自 浙江
0顶级C++大脑——fangz
2025-04-27 来自 浙江
0
有帮助,赞一个