P1364题解
可以利用动态思想,首先生成一个候选答案,然后根据候选答案节点的移动,考虑答案整体的变化。
我们的工作分为如下几步:
1.读入和预处理
我们可以在读入的过程中,记录每个节点的深度,并且保证1号节点的深度为0——虽然题目中没有明确说明,但是数据其实保证了1号节点是根节点——这样我们容易得到初始解ans。
之后我们做一个简单的预处理,得到数组sum[],sum[u]的含义是以u为根的子树的子树和。
2.计算
前言中提到,我们的思路是在一个初始答案上移动候选点以得到新的答案,当我们遍历n个节点后,最优的答案即为所求。
对于上图,假定我们已经获得了以e为候选点的候选答案,正准备移动到c点获得新的答案,我们容易发现:
- 以e为根的子树中的所有节点距离候选点的距离都增加了1
- 除去子树e的其他部分的所有节点距离候选点的距离都减少了1
于是我们得到等式 ans[c]=ans[e]+sum[e]-(tot-sum[e])
而同理,当我们从c向e移动时,即有等式 ans[e]=ans[c]-sum[e]+(tot-sum[e])
分析上述过程,算得初始答案ans的过程是O(n)的,移动并更新答案是O(1)的,共进行O(n)次,即计算过程是O(n)的,整体的时间复杂度即为O(n)。
而经过分析,我们可以从1号节点开始,向下通过搜索的方式进行遍历,即可不必考虑上述的第二种情况,得到代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100+10;
int n;
int dep[maxn];
int val[maxn],sum[maxn];
vector<int> G[maxn];
int ans,tot;
void read(){
cin>>n;
dep[1]=0;
for(int i=1;i<=n;++i){
int l,r;
scanf("%d%d%d",val+i,&l,&r);
if(l) G[i].push_back(l),dep[l]=dep[i]+1;
if(r) G[i].push_back(r),dep[r]=dep[i]+1;
ans+=val[i]*dep[i];
tot+=val[i];
}
}
int dp(int u){
if(G[u].empty()) return sum[u]=val[u];
int res=0;
for(int i=0;i<G[u].size();++i) res+=dp(G[u][i]);
return sum[u]=res+val[u];
}
void prework(){
dp(1);
}
void dfs(int u,int v,int res){
res+=tot-sum[v];
res-=sum[v];
ans=min(ans,res);
for(int i=0;i<G[v].size();++i) if(G[v][i]!=u) dfs(v,G[v][i],res);
}
void solve(){
for(int i=0;i<G[1].size();++i) dfs(1,G[1][i],ans);
cout<<ans<<endl;
}
int main(){
read();
prework();
solve();
return 0;
}