P9689 Bina. 题解
难评。
可以直接枚举树的高度,看有没有砍掉 $m$ 个点。
当 $k$ 小于树的深度时,显然节点个数为 $2^k-1$,此时编号和是容易的。
但是当 $k$ 等于树的深度时,编号和不能直接求出,因为最后一层的编号不是连续的。
令 $x=ed-st+1$。子树大小是容易通过记忆化得到的。考虑编号和,容易发现当 $x$ 是偶数时,两边的子树的形态是一致的,考虑从这里下手。
不妨设 $f_x$ 表示传入的参数为 $x$ 时,且该子树根节点为 $1$ 的编号和。
但是转移时变为了以 $2$ 为根的子树和以 $3$ 为根的子树的编号和加 $1$。
然后看它与 $1$ 为子树的编号和的变化。以 $2$ 为例,发现就是如果有 $c$ 个节点,满足 $2^p-1<c\le2^{p+1}-1$,则变化了 $\sum\limits_{i=0}^{p}4^i+2^{p+1}(c-2^p+1)$,即按层考虑。
注意 long long,赛时卡了好久。
和线段树是很像的,每层是不会超过两次查询的,然后每次合并 $sz_{ls}$ 和 $sz_{rs}$ 是 $\mathcal{O}(\log n)$。可以使用 umap 存储 $f$。
时间复杂度:$\mathcal{O}(T\log^2n)$。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
unordered_map<ll,ll> mp,f;
ll dfs(ll x){
if(mp[x])return mp[x];
if(x==1)return 1;
if(x%2==0)return mp[x]=2*dfs(x/2)+1;
return mp[x]=dfs(x/2)+dfs(x/2+1)+1;
}
ll calc(ll l,ll r){
return (l+r)*1ll*(r-l+1)/2;
}
ll work(ll x){
ll res=0;
for(int i=1;i<=31;i++)if((1ll<<i)-1<x)res+=(1ll<<(2*i-2));else{
res+=(1ll<<(i-1))*(x-((1<<(i-1)))+1);
break;
}
return res;
}
ll gh(ll x){
if(f[x])return f[x];
if(x==1)return 1;
int p=dfs(x/2);
if(x%2==0){
f[x]=2*gh(x/2)+work(p)+2*work(p)+1;
return f[x];
}
f[x]=gh(x/2+1)+gh(x/2)+2*work(p)+work(dfs(x/2+1))+1;
return f[x];
}
void solve(){
cin>>n>>m;
ll al=dfs(n);ll ans=0;
if(m>=al){
cout<<"-1"<<"\n";
return;
}
for(int i=1;i<=31;i++)
if((1ll<<i)-1<al){
if((al-(1ll<<i)+1)>=m)ans=max(ans,calc(1,(1ll<<i)-1)/i);
}else{
if(!m)ans=max(ans,gh(n)/i);
break;
}
cout<<ans<<"\n";
}
int main(){
int T;cin>>T;
while(T--)solve();
return 0;
}