恩先膜拜一下岩哥.....出的题真好.....
题目大意:
就是现在有一棵有N个点的树, N <= 64, 现在每条边都随机从0~L取一个比边权值(L <= 8), 问生成的树两两结点之间距离不超过S的概率是多少(S <= 512)
大致思路:
训练赛的时候想到了用dp[u][d]表示对于结点u其子节点到它的最远距离是d的概率作为选择的状态来进行转移, 但是当时没想出来转移过程....对于u结点有多个儿子的情况不知道该怎么办
结果是个对于我来说第一次见的做法....
每次考虑儿子结点的时候一个一个的考虑, 将考虑完的部分当做一个整体和下一个儿子结点考虑进行合并, 这样每次都相当于是两个节点...感觉真是巧妙...
具体做法写在代码注释里了, 详情看代码注释吧...
代码如下:
Result : Accepted Memory : 1908 KB Time : 46 ms
/* * Author: Gatevin * Created Time: 2015/7/26 23:12:23 * File Name: E.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define maxn 65 vector<int> G[maxn]; int n, L, S; double dp[maxn][520]; double dp2[520]; double sum[2][520]; double p; /* * 用dp[i][j]表示结点i到结点i所在子树中的距离的最大距离是j的概率(且其子树节点中两两距离差不超过S) * 初始化根节点dp[u][0] = 1, dp[u][ > 1] = 0 * 在每次向上进行转移的时候, 例如结点u具有儿子结点v1, v2, v3... vk * 那么先考虑结点v1 * 由已经得到的dp[v1][d]得到只考虑v1这个儿子的情况下dp[u][d]的所有值(O(L)枚举即可) * 然后考虑v2点, 由上一步得到的dp[u][d]和现在有的dp[v2][d]来推 * 首先O(L)处理出由v2向上得到的另外一组dp2[d2], 也就是新的v2向上得到的距离为d2的概率 * 然后枚举最长距离为maxd, 那么最长距离来自v1的话可能性是dp[u][0~maxd]*dp2[0~min(maxd, S - maxd)] * 最长距离来自v2的可能性是dp[u][0~min(maxd, S - maxd)]*dp2[0~maxd] * 对上面这两个部分求和即可, 当然注意枚举的maxd满足maxd*2 <= S的时候重复计算了dp[i][0~maxd]*dp2[0~maxd] * 然后考虑v3, 将之前得到的v1, v2视为一个整体和v3进行操作即可, 这就和只考虑v1和v2一样了 * 如果还有v4...vk同样的道理考虑即可 * 整体来说复杂度是O((n + L)*S) 一共T = 512组Case的话肯定足够了 */ void dfs(int now, int fa) { int nex; dp[now][0] = 1;//如果是叶子节点直接初始化0 for(int i = 0, sz = G[now].size(); i < sz; i++) if((nex = G[now][i]) != fa) { dfs(nex, now); //向上回溯的时候计算 memset(dp2, 0, sizeof(dp2)); for(int l = 0; l <= L; l++) for(int s = 0; s + l <= S; s++) dp2[s + l] += p*dp[nex][s]; sum[0][0] = dp[now][0]; for(int j = 1; j <= S; j++) sum[0][j] = sum[0][j - 1] + dp[now][j]; sum[1][0] = dp2[0]; for(int j = 1; j <= S; j++) sum[1][j] = sum[1][j - 1] + dp2[j]; for(int s = 0; s <= S; s++)//枚举最大长度为s { int s2 = min(s, S - s); double tmp = dp[now][s]*dp2[s]; dp[now][s] = dp[now][s]*sum[1][s2] + sum[0][s2]*dp2[s];//s来自整体的这部分还是来自节点nex传上来的 if((s << 1) <= S) dp[now][s] -= tmp; } } } int main() { int T; scanf("%d", &T); for(int cas = 1; cas <= T; cas++) { scanf("%d %d %d", &n, &L, &S); for(int i = 1; i <= n; i++) G[i].clear(); for(int i = 1; i < n; i++) { int u, v; scanf("%d %d", &u, &v); G[u].push_back(v); G[v].push_back(u); } p = 1./(L + 1); memset(dp, 0, sizeof(dp)); dfs(1, -1); double ans = 0; for(int s = 0; s <= S; s++) ans += dp[1][s]; printf("Case %d: %.6f\n", cas, ans); } return 0; }