Codeforces 2015 ACM Syrian Collegiate Programming Contest K

题目链接: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
",ans);
	}
    return 0;
}

原文地址:https://www.cnblogs.com/zhuyutian/p/5878907.html