POJ 2057 树状 DP

题意

传送门 POJ 2057

题解

对于某个节点,有屋子在其子树上与不在其子树上两种情况。求步数的最小期望,因为叶子节点数固定,遍历每一种可能的走法,求走到每一个节点(此时假设屋子在该节点)的最小步数和即可。

对于屋子在与不在节点 i i i 子树上的情况,记为 s u c i , f a i l i suc_{i},fail_{i} suci,faili ;对于其子树的叶子节点数,记为 s o n i son_{i} soni 。考虑到判断屋子不在屋子上的情况,需要搜索每一条分支

f a i l p = ∑ c h 在 p 的 子 树 上 ( f a i l c h + 2 ) fail_{p}=\sum_{ch在p的子树上}(fail_{ch}+2) failp=chp(failch+2)

对于叶子节点的统计

s o n p = ∑ c h 在 p 的 子 树 上 s o n c h son_{p}=\sum_{ch在p的子树上}son_{ch} sonp=chpsonch

在某种顺序下,求屋子在节点 p p p 子树的步数和,要考虑到走到当前处理子节点时,之前搜索的子节点要确认无屋子,且要重复当前处理子节点的叶子节点数次

s u c p = ∑ c h i 在 p 的 子 树 上 s u c c h i + s o n c h i + s o n i × ∑ c h i , c h j 在 p 的 子 树 上 , 且 j 在 i 之 前 搜 索 到 ( f a i l c h j + 2 ) suc_{p}=\sum_{ch_{i}在p的子树上}suc_{ch_{i}}+son_{ch_{i}}+son_{i}\times \sum_{ch_{i},ch_{j}在p的子树上,且j在i之前搜索到}(fail_{ch_{j}}+2) sucp=chipsucchi+sonchi+soni×chi,chjp,ji(failchj+2)

对于叶子节点

s o n i = 1 , f a i l i = s u c i = 0 son_{i}=1,fail_{i}=suc_{i}=0 soni=1,faili=suci=0

对于有 n n n 个子节点的节点,搜索全部路线需要 O ( n ! ) O(n!) O(n!),考虑优化;对于连续选择的子节点 i , j i,j i,j,其交换搜索顺序前后对其余子节点的结果无影响,先搜索 i i i 时需要满足步数和最小

s u c i + s o n i + s u c j + s o n j + ( f a i l i + 2 ) × s o n j < s u c i + s o n i + s u c j + s o n j + ( f a i l j + 2 ) × s o n i suc_{i}+son_{i}+suc_{j}+son_{j}+(fail_{i}+2)\times son_{j}<suc_{i}+son_{i}+suc_{j}+son_{j}+(fail_{j}+2)\times son_{i} suci+soni+sucj+sonj+(faili+2)×sonj<suci+soni+sucj+sonj+(failj+2)×soni

化简

( f a i l i + 2 ) × s o n j < ( f a i l j + 2 ) × s o n i (fail_{i}+2)\times son_{j}<(fail_{j}+2)\times son_{i} (faili+2)×sonj<(failj+2)×soni

对于每个节点排序即可求解最优顺序。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define abs(x) ((x) < 0 ? -(x) : (x))
#define INF 0x3f3f3f3f
#define delta 0.85
using namespace std;

#define maxn 1005
int N;
int worm[maxn];
int suc[maxn], fail[maxn], son[maxn];
vector<int> G[maxn];

bool cmp(int a, int b)
{
	return (fail[a] + 2) * son[b] < (fail[b] + 2) * son[a];
}

void dfs(int p)
{
	int n = G[p].size();
	if (n == 0)
	{
		son[p] = 1, suc[p] = fail[p] = 0;
		return;
	}
	vector<int> &ch = G[p];
	for (int i = 0; i < n; i++)
	{
		dfs(ch[i]);
	}
	sort(ch.begin(), ch.end(), cmp);
	int fsum = 0;
	for (int i = 0; i < n; i++)
	{
		int u = ch[i];
		if (worm[p])
		{
			fail[p] = 0;
		}
		else
		{
			fail[p] += fail[u] + 2;
		}
		son[p] += son[u];
		suc[p] += suc[u] + son[u] * (1 + fsum);
		fsum += fail[u] + 2;
	}
}

void solve()
{
	memset(suc, 0, sizeof(suc));
	memset(fail, 0, sizeof(fail));
	memset(son, 0, sizeof(son));
	dfs(0);
	printf("%.4lf\n", (double)suc[0] / son[0]);
}

int main()
{
	while (~scanf("%d", &N) && N)
	{
		for (int i = 0; i < N; i++)
		{
			G[i].clear();
		}
		for (int i = 0; i < N; i++)
		{
			int u;
			char c;
			scanf("%d %c", &u, &c);
			worm[i] = (c == 'Y' ? 1 : 0);
			if (i > 0)
			{
				G[u - 1].push_back(i);
			}
		}
		solve();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值