AGC007E Shik and Travel 解题报告
题目大意:\(n\) 个点的二叉树,每个点要么两个儿子,要么没有儿子,每条边有边权。
你从 \(1\) 号节点出发,走到一个叶子节点。然后每一天,你可以从当前点走到另一个叶子。最后回到 \(1\) 号节点,要求到过所有叶子并且每条边经过恰好两次。求一种遍历顺序,使得每相邻两叶子间的边权和的最大值最小。
题解
考虑二分最大值,看是否可行。这个点是一种类似等号转限制的做法。
因此我们只需考虑能否在不经过权值和大于 \(k\) 的叶子间的路径即可。这个怎么做呢?由于我们只关心距离,我们对于每个点可以得到一些数对 \((a,b)\) ,表示当前点子树中可以从距离当前点距离为 \(a\) 的叶子进入,从距离当前点距离为 \(b\) 的叶子退出。并且记 \(S_u\) 为对于 \(u\) 这个点来说形如 \((a,b)\) 的数对集合。
对于没有儿子的点,存在数对 \((0,0)\) 。而对于有儿子的点,由题目限制每条边和每个叶子恰好过两次,可知一个子树内的叶子肯定先互相到达,再离开当前子树,去与最近的子树中的叶子互达。因此我们得到一个非叶子节点的数对,等价于我们将其左右子树的某两个叶子相连接,用左右子树的数对表示,可以得到对于一个子树的数对 \((a,b)\) 和另一个子树的数对 \((c,d)\) ,当 \(b+c+val[u][0]+val[u][1]\le k\) 时(\(val[u][0/1]\) 表示 \(u\) 向左右儿子的边权),这两个数对可以合并,得到数对 \((a+val[u][0],b+val[u][1])\) ,这是默认 \((a,b)\) 为左子树数对,反过来的话 \(val\) 换一下就好。
但是直接暴力合并数对是寄的。我们考虑优化,显然对于两个点对 \((a,b)\) 和 \((c,d)\) ,如果 \(a\le c,b\le d\) ,那么可以知道 \((c,d)\) 是无用的。其次由于我们希望合并出来的数对的两个值都尽量小,且每个数对 \((a,b)\) 都满足 \(a+b\le k\) ,我们对于一个子树中的数对肯定有唯一的一个另一个子树的数对与之对应来得到最小的数对。
我们对于数对按照 \(a\) 从小到大排序,那么由于 \(a+b\le k\) 显然 \(b\) 递减,我们考虑决策单调性来合并即可。
然后我们有决策单调性的话,显然复杂度就是数对总数的,然后由于是一个二叉树,易证复杂度 \(O(n\log n)\) ,总复杂度 \(O(34n\log n)\)。(\(34\) 是二分复杂度)
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout);
#define gc getchar
#define pc putchar
namespace IO{
inline bool blank(const char &c){
return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
}
inline void gs(char *s){
char ch=gc();
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {*s++=ch;ch=gc();}
*s=0;
}
inline void gs(std::string &s){
char ch=gc();s+='#';
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {s+=ch;ch=gc();}
}
inline void ps(char *s){
while(*s!=0) pc(*s++);
}
inline void ps(const std::string &s){
for(auto it:s)
if(it!='#') pc(it);
}
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p*=0.1;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=(1<<17)+3;
int n;
int ch[N][2],val[N][2];
struct node{
int a,b;
};
std::vector<node>S[N],psc;
void dfs(int u,ll k){
S[u].clear();
if(!ch[u][0]){
S[u].push_back({0,0});
return;
}
dfs(ch[u][0],k);
dfs(ch[u][1],k);
int lc=ch[u][0],rc=ch[u][1];
psc.clear();
int j=-1;
for(auto it:S[lc]){
while(j+1<S[rc].size() and S[rc][j+1].a+it.b+val[u][1]+val[u][0]<=k){
++j;
}
if(j>=S[rc].size() ) continue;
if(S[rc][j].a+it.b+val[u][1]+val[u][0]>k) continue;
psc.push_back({it.a+val[u][0],S[rc][j].b+val[u][1]});
}
j=-1;
for(auto it:S[rc]){
while(j+1<S[lc].size() and S[lc][j+1].a+it.b+val[u][1]+val[u][0]<=k){
++j;
}
if(j>=S[lc].size() ) continue;
if(S[lc][j].a+it.b+val[u][1]+val[u][0]>k) continue;
psc.push_back({it.a+val[u][1],S[lc][j].b+val[u][0]});
}
int sz=psc.size();
std::sort(psc.begin(),psc.end(),[](node x,node y){
return x.a<y.a;
});
for(int i=0;i<sz;++i){
if(i!=0){
if(psc[i-1].b<=psc[i].b){
continue;
}
}
S[u].push_back(psc[i]);
}
}
inline bool check(int u,ll k){
dfs(1,k);
return S[1].size();
}
int main(){
filein(a);fileot(a);
read(n);
for(int u=2;u<=n;++u){
int f,w; read(f,w);
if(!ch[f][0]) ch[f][0]=u,val[f][0]=w;
else ch[f][1]=u,val[f][1]=w;
}
ll l=0,r=1ll*(1ll<<17)*(1ll<<17),hf=r;
while(l<=r){
ll mid=(l+r)>>1;
if(check(1,mid) ){
r=mid-1;hf=mid;
}else{
l=mid+1;
}
}
printf("%lld\n",hf);
return 0;
}