AGC023F 01 on Tree
AGC023F
一棵树,每个点上有\(0\)或\(1\)的点权。
你要钦定一个遍历顺序,使得:每个点遍历之前,它的所有祖先都被遍历过。
按照遍历顺序得到一个点权序列,求这个点权序列的逆序对的最小值。
\(n\le 2*10^5\)
一天连续看两道题的题解有点心虚。。。
正解似曾相识?
关键思路:将“价值”最大的节点和父亲合并。
每个节点代表一个遍历的序列,每次选择一个点和父亲合并(意味着选了父亲之后立刻选它),一直这样操作直到剩下一个点。可以发现这样操作的方案可以对应上所有的遍历方案。
考虑两个序列的合并:记第一个序列有\(a_1\)个\(0\),有\(b_1\)个\(1\);第二个同理。第一个放在第二个前,贡献为\(b_1a_2\);反之贡献为\(b_2a_1\)。
假如第一个放第二个前更优,那么\(b_1a_2<b_2a_1\),也就是\(\frac{a_2}{b_2}<\frac{a_1}{b_1}\)。
根据\(\frac{a_i}{b_i}\)排序。于是就可以决定局部(菊花图)的先后顺序。
由于合并之后,父亲的\(a\)和\(b\)会变化,所以要扩展一下:找到整张图的\(\frac{a_i}{b_i}\)最大的点,将它和父亲合并。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
#define ll long long
int n;
int fa[N],a[N],b[N];
int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
struct Info{
int a,b,x;
} h[N*2];
bool cmph(Info son,Info fa){return (ll)fa.b*son.a<(ll)son.b*fa.a;}
int nh;
int main(){
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
for (int i=2;i<=n;++i)
scanf("%d",&fa[i]);
for (int i=1;i<=n;++i){
int x;
scanf("%d",&x);
(x==0?a[i]:b[i])=1;
}
for (int i=1;i<=n;++i)
h[nh++]={a[i],b[i],i};
make_heap(h,h+nh,cmph);
for (int i=1;i<=n;++i)
dsu[i]=i;
ll ans=0;
while (nh){
int x=h[0].x;
pop_heap(h,h+nh--,cmph);
if (fa[x] && dsu[x]==x){
int y=getdsu(fa[x]);
dsu[x]=y;
ans+=(ll)a[x]*b[y];
a[y]+=a[x],b[y]+=b[x];
h[nh++]={a[y],b[y],y};
push_heap(h,h+nh,cmph);
}
}
printf("%lld\n",ans);
return 0;
}