Description
Solution
容易想到一个贪心的思路,记得有个叫什么国王的游戏的题,大概就是考虑邻项作差。如果 (x) 在前面比 (y) 在前面更优的话,一定有
[b_{x}(a_y+S)+b_yS > b_y(a_x+S)+b_xS
]
化简可得
[b_xa_y > b_ya_x
]
所以最有的情况是按这个排序再依次取。如果扩展到树上的话,就可以用一个堆维护,每次取出最大的。
但是这样实际上是错的,因为有依赖关系,即可能有一个点的 (frac{b}{a}) 很小,但它的儿子却很大。这和蓝书上一道染色的题是一个道理。一个性质是,当前所有节点权值最大的点一定会在其父亲被选取之后立即被选择。那么就可以根据这个倒序更新答案,每次把权值最大的点合并到它的父亲,同时计算贡献。可以用并查集维护。
#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
const int N=3e5+7;
ll a[N],b[N];
int Fa[N],fa[N],sz[N];
struct Node{
int pos,sz; ll x,y;
Node(int pos_=0,int sz_=0,ll x_=0,ll y_=0):
pos(pos_),sz(sz_),x(x_),y(y_){}
bool operator <(const Node &X) const{
return y*X.x<X.y*x;
}
};
int find(int x){
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
priority_queue<Node> Q;
int main(){
int n=read(); fa[1]=sz[1]=1;
for(int i=2;i<=n;i++) Fa[i]=read();
for(int i=1;i<=n;i++){
a[i]=read(),b[i]=read();
if(i!=1) Q.push(Node(i,sz[fa[i]=i]=1,a[i],b[i]));
}
ll ans=0;
while(!Q.empty()){
Node t=Q.top(); Q.pop(); int u=t.pos;
if(t.sz!=sz[u]) continue;
int x=find(Fa[u]),y=find(u);
if(x==y) continue;
ans+=a[y]*b[x];
a[x]+=a[y],b[x]+=b[y];
sz[x]+=sz[y],fa[y]=x;
if(x!=1) Q.push(Node(x,sz[x],a[x],b[x]));
}
printf("%lld",ans);
}