CF1905E One-X
考虑在 \(n\) 个节点的树中,树根作为 \(lca\) 对答案的贡献,显然就是在左子树的叶子中选出一个非空集的方案乘上右子树的方案。
\[w(n, id) = id \cdot (2 ^ {L\_Leaf} - 1) \cdot (2 ^ {R\_Leaf} - 1)
\]
进而得到一个 \(O(N)\) 的 \(dp\),其中 \(ls = \lceil \dfrac{n}{2} \rceil\),\(rs = \lfloor \dfrac{n}{2} \rfloor\)。
\[dp(n, id) = w(n, id) + dp(ls, 2\cdot id) + dp(rs, 2\cdot id + 1)
\]
观察状态参数,可以发现在一次求解中 \(id\) 有 \(O(N)\) 个,而 \(n\) 只有 \(O(\log N)\) 个,一个直观的想法是把 \(id\) 从状态方程中剥离出去。
不妨手玩一下这个式子。
\[\begin{aligned}
dp(n, id) &= w(n, id) + dp(ls, 2\cdot id) + dp(rs, 2\cdot id + 1) \\
&= t(n) \cdot id + dp(ls, 2\cdot id) + dp(rs, 2\cdot id + 1) \\
&= t(n) \cdot id + t(ls) \cdot 2\cdot id + t(rs) \cdot (2\cdot id + 1) + (dp \ldots) \\
&= (t(n) + 2 \cdot t(ls) + 2 \cdot t(rs) + 4 \cdot \ldots) \cdot id + (t(rs) + \ldots)
\end{aligned}
\]
能够得到整个状态方程为 \(id\) 的一次函数,只需维护 \(k(n)\) 和 \(b(n)\) 就好了。
即
\[dp(n, id) = k(n) \cdot id + b(n)
\]
由于
\[\begin{aligned}
dp(n, id) &= t(n) \cdot id + dp(ls, 2\cdot id) + dp(rs, 2\cdot id + 1) \\
&= t(n) \cdot id + k(ls) \cdot 2\cdot id + b(ls) + k(rs) \cdot (2\cdot id + 1) + b(rd)
\end{aligned}
\]
所以
\[k(n) = t(n) + 2 \cdot k(ls) + 2 \cdot k(rs)
\]
\[b(n) = b(ls) + k(rs) + b(rs)
\]
map<ll, pll> mp;
pll calc(ll n) {
if(mp.find(n) != mp.end()) return mp[n];
auto [lk, lb] = calc((n + 1) / 2);
auto [rk, rb] = calc(n / 2);
ll t = (q_pow(2, (n + 1) / 2) - 1) * (q_pow(2, n / 2) - 1) % P;
ll k = (t + 2 * lk + 2 * rk) % P;
ll b = (lb + rk + rb) % P;
/*
f[n][id] = t * id + f[ls][lsid] + f[rs][rsid]
*/
return mp[n] = {k % P, b % P};
}
void solve() {
ll n;
cin >> n;
auto [k, b] = calc(n);
cout << (k + b) % P << '\n';
}
int main() {
int T; cin >> T;
mp[1] = {1, 0};
while(T --) solve();
return 0;
}
ccf 赶紧支持 c++17
。