Codeforces 2015 ACM Syrian Collegiate Programming Contest K - Betrayed 连续期望+树上dp
题目链接:http://codeforces.com/gym/101086/problem/K
题意:每个kase有c棵树,每棵树有n个节点。你需要按顺序历遍c棵树。历遍任意一棵树都需要1s。当历遍某一棵树的时候,可以选任意节点为根节点(每个节点被选到的概率都相等),如果以某个节点为根节点并且树的最大深度超过K时,就会爆栈,然后必须用额外3s重启程序,再从第一棵树开始历遍。问在没有爆栈的情况下跑完c棵树所用时间的期望。<题解:这道题关键在于理清题目的过程,然后就是一道求树上每个节点最远路径加个连续期望。题目要求跑完c棵树的时间期望,当在跑某棵树爆栈的时候,上面已经说了,程序会重新从第一棵树开始历遍,并且程序再次启动需要3s,也就是说爆栈后时间是重新算的,只不过需要额外的3s来启动程序。那么我们就可以考虑当程序跑完第i棵树的时候,会产生两种结果:一是没有爆栈进入到下一棵树,此时额外耗费的时间是0;二是爆栈了,然后3s后再重新从第一棵树开始跑,时间重新算(别忘了存在3s的启动时间),那么额外耗费的时间就是3s。然后我们设有p的概率是没有爆栈的,那么第i棵树任意选一个节点对跑完这棵树(包括爆栈和不爆栈)的时间期望的贡献就是(0*p+(1-p)*3),也就是每一个点对跑完第i棵树的贡献,那么从第一棵树到第i棵树的总时间为(ans[i-1]+1+(1-p)*3),这是从n个点选一个点的期望。那么我们再考虑平均选多少个点(也就是爆栈多少次)才能通过这棵树。显然,我们设有ok个节点是不会产生爆栈的,那么平均情况就是n/ok。那么ans[i]=(ans[i-1]+1+(1-p)*3)*n/ok;p=ok/n。具体看代码。
那么现在就应该考虑求某棵树的ok个不会产生爆栈的节点,也就是要求树上每个节点的最长路径问题。题目中节点数是1e5,也就是说绝对不能暴力瞎搞。那么这就涉及了一个裸的算法,可以O(n)求出来。我们来考虑树上的最长路径,设端点为A,B,也就是从A节点到B节点的距离是树上的最长路径,那么很明显树上任意一个点的最长路径肯定是从该点到A或者B的路径长度。为什么?举个例子,设树上任意一个节点为C(包括A,B),按照上面所说最长路径要么是AC,要么是CB。假如现在有节点D使的CD的长度大于AC(或者CB),那么我们知道树上最长路径是AB,如果CD是以C为起点的最长路径的话,那么AC+CD>AB(或者CD+CD>AB),此时最长路径就应该是AD(或者BD)了,所以是不存在这个情况的,也就是说结论成立。然后我们考虑当判断编号为a的节点的最长路径的时候,就只有两种情况了,一是从父节点过来的最长路径,二是节点a向下走子节点的最长路径。具体实现看代码。
代码:
/*===============================================================
* Filename:k.cpp
* Author:zhuyutian
* Date:2016年09月16日
================================================================*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=100005;
vector<int>g[N];
int mxd[N][2];
int dep[N],low[N];
int T,c,k,n;
int cnt;
void init()
{
memset(dep,0,sizeof(dep));
memset(low,0,sizeof(low));
memset(mxd,0,sizeof(mxd));
for(int i=1;i<=n;i++)
g[i].clear();
}
void dfs1(int u,int f)
{
low[u]=dep[u];
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==f) continue;
dep[v]=dep[u]+1;
dfs1(v,u);
low[u]=max(low[v],low[u]);
if(low[v]>mxd[u][0])
{
mxd[u][1]=mxd[u][0];
mxd[u][0]=low[v];
}
else if(low[v]>mxd[u][1])
mxd[u][1]=low[v];
}
}
void dfs2(int u,int f,int up)
{
if(mxd[u][0] - dep[u] <= k && up <= k ) cnt++;//向上走(父节点)和向下走(子节点)
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==f)
continue;
int md=mxd[u][0];
if(md==mxd[v][0]) md=mxd[u][1];
dfs2(v,u,max(up+1,md-dep[u]+1));
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&c,&k);
double ans=0;
for(int i=1;i<=c;i++)
{
scanf("%d",&n);
init();
int a;
for(int i=2;i<=n;i++)
{
scanf("%d",&a);
g[i].push_back(a);
g[a].push_back(i);
}
cnt=0;
dfs1(1,-1);
dfs2(1,-1,0);
double p=1.0*cnt/n;
ans=(ans+1+(1-p)*3.0)/p;//不管爆不爆栈,都需要1s
}
printf("%.4lf\n",ans);
}
return 0;
}