题目描述
求一棵 $[1,n]$ 的线段树的最大匹配数目与方案数。
$n\le 10^{18}$
题解
树形dp+记忆化搜索
设 $f[l][r]$ 表示根节点为 $[l,r]$ 的线段树,匹配选择根节点的最大匹配&方案数,$g[l][r]$ 表示根节点为 $[l,r]$ 的线段树,匹配不选择根节点的最大匹配&方案数。那么这是一个很普通的树形dp。
注意到区间长度相等的线段树的结果是一样的,且每层至多有两种区间长度不同的区间(参考 这题 ),因此直接以区间长度为状态进行记忆化搜索即可。
这里偷懒使用了map,时间复杂度 $O(\log^2 n)$
#include <map> #include <cstdio> #define mod 998244353 using namespace std; typedef long long ll; struct data { ll x , y; data() {} data(ll a , ll b) {x = a , y = b;} data operator+(const data &a)const {return data(x + a.x , y * a.y % mod);} data operator*(const data &a)const { if(x > a.x) return *this; if(x < a.x) return a; return data(x , (y + a.y) % mod); } }; struct node { data f , g; node() {} node(data a , data b) {f = a , g = b;} }; map<ll , node> mp; node dfs(ll n) { if(mp.find(n) != mp.end()) return mp[n]; node l = dfs(n - (n >> 1)) , r = dfs(n >> 1); return mp[n] = node((l.f + r.g + data(1 , 1)) * (l.g + r.f + data(1 , 1)) * (l.g + r.g + data(1 , 2)) , (l.f + r.f) * (l.f + r.g) * (l.g + r.f) * (l.g + r.g)); } int main() { mp[1] = node(data(-1 , 0) , data(0 , 1)); ll n; scanf("%lld" , &n); node tmp = dfs(n); data ans = tmp.f * tmp.g; printf("%lld %lld\n" , ans.x , ans.y); return 0; }