CF1905E One-X 题解

先考虑 nn 比较小的时候怎么做。

非常显然地,按照线段树建树,枚举每个点作为 LCA 的答案。假设当前节点 uu,对应区间 [l,r][l,r],区间长度 len=rl+1len = r-l+1,那么对答案的贡献为 u×(2len21)×(2len21)u \times (2^{\lceil \frac{len}{2}\rceil} - 1) \times (2^{\lfloor \frac{len}{2}\rfloor} - 1),即在左右子树的叶子节点中各选若干,且保证左右两侧都至少选了一个点。答案即为每个点贡献之和。

上述的朴素做法复杂度为 O(n)O(n)。通常在线段树或者完全二叉树之类的比较优秀的树形结构上求一些贡献,但不能完全建出树时,常用方法是记忆化。例如在线段树上有经典结论,每个点对应的不同的 len=rl+1len=r-l+1 的数量量级为 O(logn)O(\log n)。而上文的做法中确实需要用 lenlen 计算贡献。所以不妨考虑记忆化搜索。

但现在问题是,上文的贡献中还有 uu。而 u[1,n]u \in [1,n],不同数量很大。于是我们想把贡献中的 uu 分出来。我们猜测以 (u,len)(u,len) 为根的子树贡献是一个关于 uu 的一次函数,其中 lenlen 是定值。这样我们就可以记忆化的过程中维护每个 lenlen 对应的一次函数即可。

现在的问题是,为什么是关于 uu 的一次函数。考虑归纳证明:

f(u,len)f(u,len) 为根节点为 uu,根的区间 [l,r][l,r],区间长度 len=rl+1len=r-l+1 时的整棵子树答案。

首先,len=1len=1 时,必有 f(u,1)=uf(u,1) = u。这很显然是一个 b=0b=0 的一次函数。

考虑 len>1len > 1。我们假设 uu 的左右儿子的 f(u,len)f(u,len) 都是关于 uu 的一次函数。考虑 f(u,len)f(u,len) 的转移形式:f(u,len)=f(lson,len2)+f(rson,len2)+u×(2len21)×(2len21)f(u,len) = f(lson,\lceil \frac{len}{2} \rceil) + f(rson,\lfloor \frac{len}{2} \rfloor) + u \times (2^{\lceil \frac{len}{2}\rceil} - 1) \times (2^{\lfloor \frac{len}{2}\rfloor} - 1)

进一步,由于左右儿子的 ff 都是一次函数,且 lson=2ulson = 2urson=2u+1rson = 2u + 1

所以 f(u,len)=klen2×2u+blen2+klen2×(2u+1)+blen2+u×(2len21)×(2len21)f(u,len) = k_{\lceil \frac{len}{2} \rceil} \times 2u + b_{\lceil \frac{len}{2} \rceil} + k_{\lfloor \frac{len}{2} \rfloor} \times (2u+1) + b_{\lfloor \frac{len}{2} \rfloor} + u \times (2^{\lceil \frac{len}{2}\rceil} - 1) \times (2^{\lfloor \frac{len}{2}\rfloor} - 1)

其中 kk 是斜率,bb 是截距。

发现这个式子关于 uu 是一次的,把式子整理成关于 uu 的一次函数就可以发现 klenk_{len}blenb_{len} 关于左右儿子的递推式。

具体地:klen=2×(klen2+klen2)+(2len21)×(2len21),blen=blen2+blen2+klen2k_{len} = 2 \times (k_{\lceil \frac{len}{2} \rceil} + k_{\lfloor \frac{len}{2} \rfloor}) + (2^{\lceil \frac{len}{2}\rceil} - 1) \times (2^{\lfloor \frac{len}{2}\rfloor} - 1), b_{len} = b_{\lceil \frac{len}{2} \rceil} + b_{\lfloor \frac{len}{2} \rfloor} + k_{\lfloor \frac{len}{2} \rfloor}。读者自证不难。

使用 map 记忆化即可。复杂度 O(Tlog2n)O(T \log^2 n)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <map>
using namespace std;

const long long MOD = 998244353ll;

using ll = long long;

int t;
ll n;
map<ll, pair<ll, ll>> mp;

ll qpow(ll a, ll b)
{
	ll res = 1ll, base = a;
	while (b)
	{
		if (b & 1ll) res = res * base % MOD;
		base = base * base % MOD;
		b >>= 1ll;
	}
	return res;
}

pair<ll, ll> query(ll u, ll len)
{
	if (mp.count(len)) return mp[len];
	if (len == 1) return (mp[len] = make_pair(1ll, 0ll));
	if (len == 0) return (mp[len] = make_pair(0ll, 0ll));
	ll x = (len + 1) / 2ll, y = len / 2ll;
	auto lft = query(u << 1ll, x), rit = query(u << 1ll | 1ll, y);
	auto res = make_pair(((2ll * lft.first % MOD + 2ll * rit.first % MOD) % MOD + ((qpow(2ll, x) - 1) * (qpow(2ll, y) - 1) % MOD)) % MOD, (lft.second + rit.second + rit.first) % MOD);
	return mp[len] = res;
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> t;
	while (t--)
	{
		cin >> n;
		mp.clear();
		auto g = query(1, n);
		cout << (g.first + g.second) % MOD << "\n";
	}
	return 0;
}
posted @   HappyBobb  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示