题解:洛谷 P9128([USACO23FEB] Fertilizing Pastures G)
1. Description
有 \(N\) 个顶点的树,经过节点之间的每一条边都需要 \(1s\)。每个顶点一开始的权值均为 \(0\),第 \(i\) 个点的权值的增长速率为 \(a_i/s\)。FJ 从 \(1\) 号顶点出发遍历整棵树。当 FJ 走到某个节点时,若该节点的权值为 \(x\),则需要支出大小为 \(x\) 的费用。(当然,只需在第一次经过该节点时需要支出。)
给出一个参数 \(T\):
-
若 \(T=0\),FJ 必须回到 \(1\) 号节点。
-
若 \(T=1\),FJ 可以在任意节点结束他的遍历。
求遍历所有节点的最小时间和此时需要付出的最小的费用。
2. Solution
\(T=0\) 时这个问题十分简单,我们考虑需要在最小时间内遍历整棵树并返回根节点,那么一条边就只会被遍历两次,所需时间为 \(2(n-1)\)。
最小费用则是一个树形 DP,不妨设 \(sum_u\) 表示以 \(u\) 为根的子树中所有节点的增长速率之和,\(siz_u\) 表示以 \(u\) 为根的子树中的节点数,\(f_u\) 表示从 \(u\) 出发,遍历完以 \(u\) 为根的子树并返回 \(u\) 的最小代价,注意,此时认为从 \(u\) 出发时的时刻为 \(0\)。
那么当我们以一个确定顺序 \(v_1,v_2,\cdots,v_{|son_u|}\) 遍历 \(u\) 的子树时,花费就是 \(\sum_{i=1}^{|son_u|} f_{v_i}+(2\times \sum_{j=1}^{i-1} siz_{v_j}+1)\times sum_{v_i}\),后一个部分的系数可以在遍历的过程中计算,那么时间复杂度就是 \(O(|son_u|)\),但是我们并不知道最优的顺序是什么,所以还需要暴力枚举所有顺序,时间复杂度就是 \(O(|son_u|!\times |son_u|)\),这显然是不够优秀的时间复杂度,所以我们考虑优化,这里显然可以使用贪心来确定一个最优顺序,直接邻项交换法,令之前已经耽误了 \(c\) 的时间,需要交换 \(x,y\) 两项,那么这两项在交换前后的贡献和就是 \(f_x+(c+1)\times sum_x+f_y+(c+1+2\times siz_x)\times sum_y\) 和 \(f_y+(c+1)\times sum_y+f_x+(c+1+2\times siz_y)\times sum_x\), 不需要交换当且仅当:
也就是:
那么直接排序然后遍历即可,时间复杂度为 \(O(n\log n)\)。
\(T=1\) 的时候可能有些复杂,考虑最小时间一定是在一个深度最深的节点结束遍历时得到的,所以所需时间为 \(2(n-1)-\max_{i=1}^{n} dep_i\),其中 \(dep_1=0\)。
最小费用我们延续 \(T=0\) 时的想法,设 \(mxdep_u\) 表示以 \(u\) 为根的子树中,所有节点的最大深度,\(f_{u,0/1}\) 分别表示从 \(u\) 开始,以最短时间遍历完以 \(u\) 为根的子树后不返回/返回 \(u\) 的最小费用,其中 \(f_{1,u}\) 的转移和 \(T=1\) 时 \(f_u\) 的转移一致,我们考虑 \(f_{0,u}\) 的转移。
显然,我们为了保证花费时间最小,一定会选择一棵最大深度最小的子树进入之后不返回,我们假定这棵子树的根节点为 \(v\),在排序后的 \(son_u\) 数组中的下标为 \(pos\),那么我们首先在遍历时跳过 \(v\) 这棵子树,减少的贡献分为两部分:
- \(v\) 本身的贡献,也就是 \(f_{1,v}+(2\times \sum_{i=1}^{pos-1} siz_{v_i}+1)\times sum_v\),这个贡献可以在转移 \(f_{1,u}\) 的过程中统计。
- \(v\) 花费的时间对后面子树的贡献,也就是 \(2\times siz_v\times (\sum_{i=pos+1}^{|son_u|} sum_{v_i})\),所以我们可以倒序遍历 \(son_u\),那么后缀和就可以直接统计。
然后再最后遍历这棵子树,增加的贡献就是 \(f_{0,v}+sum_v\times [2\times (siz_u-siz_v-1)+1]\),所以 \(f_{0,u}=\min_{v\in son_u}f_{1,u}-val_v-2\times siz_v\times sufsum+f_{0,v}+sum_v\times (2siz_u-2siz_v-1)[mxdep_v=mxdep_u]\),其中 \(val_v\) 表示 \(v\) 对 \(f_{0,u}\) 的贡献,\(sufsum\) 表示后缀 \(sum\) 的和。
3. Code
/*by qwer6*/
/*略去缺省源与快读快写*/
const int N=2e5+5,inf=8e18;
int n,type;
int a[N];
struct Graph{
struct Edge{
int v,nxt;
}e[N];
int cnt_edge;
int head[N];
void AddEdge(int u,int v){
e[++cnt_edge]={v,head[u]};
head[u]=cnt_edge;
}
}G;
namespace Subtask1{//必须回到 1 结束遍历
int f[N],siz[N],sum[N];
vector<int>son[N];
bool cmp(int x,int y){
return siz[x]*sum[y]<siz[y]*sum[x];
}
void dfs(int u){
siz[u]=1,sum[u]=a[u];
for(int i=G.head[u],v=G.e[i].v;i;i=G.e[i].nxt,v=G.e[i].v){
dfs(v);
siz[u]+=siz[v];
sum[u]+=sum[v];
son[u].push_back(v);
}
int c=0;
sort(son[u].begin(),son[u].end(),cmp);
for(int v:son[u]){
f[u]+=f[v]+(c+1)*sum[v];
c+=2*siz[v];
}
}
void solve(){
dfs(1);
write((n-1)*2),Spa,write(f[1]),Nxt;
}
}
namespace Subtask2{//可以在任意节点结束遍历
int dep[N],siz[N],sum[N],mxdep[N],val[N],f[2][N];
vector<int>son[N];
bool cmp(int x,int y){
return siz[x]*sum[y]<siz[y]*sum[x];
}
void dfs(int u){
siz[u]=1,sum[u]=a[u],mxdep[u]=dep[u];
for(int i=G.head[u],v=G.e[i].v;i;i=G.e[i].nxt,v=G.e[i].v){
dep[v]=dep[u]+1;
dfs(v);
tomax(mxdep[u],mxdep[v]);
siz[u]+=siz[v];
sum[u]+=sum[v];
son[u].push_back(v);
}
int c=0;
sort(son[u].begin(),son[u].end(),cmp);
for(int v:son[u]){
val[v]=f[1][v]+(c+1)*sum[v];
f[1][u]+=val[v];
c+=2*siz[v];
}
f[0][u]=inf;
for(int i=son[u].size()-1,suf=0,v;i>=0;i--){
v=son[u][i];
if(mxdep[v]!=mxdep[u]){
suf+=sum[v];
continue;
}
c=2*(siz[u]-siz[v]-1);
tomin(f[0][u],f[1][u]-val[v]-2*siz[v]*suf+f[0][v]+(c+1)*sum[v]);
suf+=sum[v];
}
if(f[0][u]==inf)f[0][u]=0;
}
void solve(){
dfs(1);
write((n-1)*2-mxdep[1]),Spa,write(f[0][1]),Nxt;
}
}
signed main(){
read(n),read(type);
for(int i=2,fa;i<=n;i++){
read(fa),read(a[i]);
G.AddEdge(fa,i);
}
if(type==0)Subtask1::solve();
else Subtask2::solve();
}

浙公网安备 33010602011771号