POJ 2057 The Lost House
智商掉线*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_{y\in 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}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\n",f[rt]/(db)ss[rt]);
}
return 0;
}