智商掉线*2...
可以看成求一种遍历叶子的顺序,使得以每个叶子为终点的路径的长度之和最小.考虑设(f_x)表示(x)子树内,以(x)为起点到所有叶子的路径长度之和的最小值,(g_x)表示从(x)父亲走进子树(x)然后遍历完一遍后出来的步数(也就是没走进终点所在子树而要浪费的步数),(s_x)表示(x)子树内叶子节点个数
先考虑(g_x),如果没有worm那么就是子树大小(*2).否则因为worm会告诉snail终点不在这个子树内,那么要遍历的联通块大小就是所有worm节点断掉儿子的边以后的联通块大小,(g_x)就是这个联通块大小(*2)
然后是(f_x),因为终点可能在所有儿子子树内,所以后遍历的子树的步数要加上前面遍历子树浪费的步数(g_y),转移要枚举遍历儿子的顺序,然后大概长这样$$f_x=(sum_{yin son_of_x}f_y )+(min_{{p1,p2,p3...p_{cnt}}=son_of_x}sum_{i=1}{cnt}s_{p_i}*(1+sum_{j=1}{i-1}g_{p_j}))$$
考虑后半部分,这个式子有点国王游戏,考虑交换两个相邻儿子的枚举顺序来优化答案,首先这不会对其他的儿子贡献产生影响,然后如果(i)号放(i+1)号儿子前面更优,相当于这种情况的和比(i)放后面的和要小,进一步化简可以得到要满足(s_ig_{i+1}>s_{i+1}g_i)这个条件,所以可以按照这个为关键字排序,然后一遍扫过去得到(f_x).最后输出(frac{f_{root}}{s_{root}})
#include<bits/stdc++.h>
//poj 不能用bits头,请选c++编译器
#define LL long long
#define uLL unsigned long long
#define db double
using namespace std;
const int N=1000+10;
int rd()
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
int to[N<<1],nt[N<<1],hd[N],tot=1;
void add(int x,int y)
{
++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot;
++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot;
}
char cc[N];
bool v[N],bb[N];
int n,rt,sz[N],ss[N],st[N],tp;
bool cmp(int aa,int bb){return 1ll*ss[aa]*sz[bb]>1ll*ss[bb]*sz[aa];}
LL f[N];
void dp(int x,int ffa)
{
sz[x]=1,ss[x]=f[x]=0;
if(ffa&&!nt[hd[x]]){ss[x]=1;return;}
for(int i=hd[x];i;i=nt[i])
{
int y=to[i];
if(y==ffa) continue;
dp(y,x),ss[x]+=ss[y];
if(!v[x]) sz[x]+=sz[y];
}
tp=0;
for(int i=hd[x];i;i=nt[i])
{
int y=to[i];
if(y==ffa) continue;
st[++tp]=y,f[x]+=f[y]+ss[y];
}
sort(st+1,st+tp+1,cmp);
LL dt=0;
for(int i=1;i<=tp;++i)
{
int y=st[i];
f[x]+=ss[y]*dt;
dt+=sz[y]*2;
}
}
int main()
{
int T=rd();
while(T--)
{
n=rd();
for(int i=1;i<=n;++i) hd[i]=0;
tot=1;
for(int i=1;i<=n;++i)
{
int y=rd();
if(y>0) add(i,y);
else rt=i;
scanf("%s",cc);
v[i]=cc[0]=='Y';
}
dp(rt,0);
printf("%.4lf
",f[rt]/(db)ss[rt]);
}
return 0;
}