bzoj·入门OJ·统计损失

初见安~这里是传送门:入门OJ P2004

Description

SJY有一天被LLT紧急召去计算一些可能的损失。LLT元首管理的SHB国的交通形成了一棵树,现在将会出现一颗陨石
砸在SHB国中,并且陨石砸毁的必定是SHB国构成的交通树上的一条路径。SHB国的损失可表示为被砸毁的路径上的
所有城市价值之积。现在还暂时无法确定陨石的掉落路线,所以LLT元首希望SJY能够告诉他SHB国在受到每一种砸
毁方式后会受到的损失之和模10086之后的值。注意:单独一个节点也被认为是合法的路径。

Input

第1行一个数n,表示城市数,n<=100000
第2行n个数,第i个数表示第i个城市的价值。
第3到n+1行,每行两个数u,v,表示城市u,v之间有一条道路。

Output

包含一个数,表示SHB国将受到的损失之和。

Sample Input

5
7 6 6 1 1
1 2
2 3
2 4
1 5

Sample Output

778

Sol

O(n^2)的做法很显然,从每个点出发枚举到其他所有点的路径的乘积并累和。【我当时写的WA了两个点,至今感到匪夷所思】

也很明显,这个题只能是用O(n)的树形dp来解。

所以关键问题就是——怎么dp………【废话。

【以下思路及代码参考:这篇题解

其实dp需要考虑的关键问题就是状态。路径的状态只有两种——直链和折链。直链的话很好处理,就是从下往上每个子树的值乘上当前点的值再加上当前点的值。表达出来就是:dp[u] = \sum dp[v] *value[u] +value[u]。这里应该比较好理解,就是依次网上,以各个点为下面那个点并以当前点为上面那个点时的链。所以对于直链的答案统计起来就是ans = \sum{dp[i]}

那么对于折链呢?其实也就相当于是两条直链。把所有子树的答案乘起来就行了【??!啪】

假设处理到了子树v,那么让这个子树负责一条半链,另外半条让其他子树负责,所以此时答案也就是\sum{dp[son[u]]*dp[v]]}(son[u]!=v)。当然,还要注意去重和对于u的计算这些细节。这里就不提了【大雾

折链的答案不能加入dp里带进去往上推的。因为很显然如果带进去了那其中有些方案就不是单纯的两点之间的路径了。

 

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
const int mod = 10086;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge {
	int to, nxt;
	edge() {}
	edge(int tt, int nn) {to = tt, nxt = nn;}
}e[maxn << 1];

int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v ,head[u]); head[u] = k++;}

int n, va[maxn];
ll ans = 0, dp[maxn];
bool vis[maxn];
void dfs(int u, int fa) {
	dp[u] = va[u];//记得要初始化为u的value
	ll sum = 0;
	for(int i = head[u]; ~i; i = e[i].nxt) {
		register int v = e[i].to; if(v == fa) continue;
		dfs(v, u);
		dp[u] = (dp[u] + dp[v] * va[u]) % mod;//dp累计直链的答案
		ans = (ans + sum * dp[v]) % mod;//因为折链的统计不能加入dp,那就直接加入ans吧。这里v子树没有包含u节点
		sum = (sum + dp[v] * va[u]) % mod;//一定要在ans计算过后再累计进去
	}
	ans = (ans + dp[u]) % mod;//最后加上直链的答案
}

signed main() {
	memset(head, -1, sizeof head);
	n = read();
	ll T = 0;
	for(int i = 1; i <= n; i++) va[i] = read(), T += va[i];//T没啥用,自行忽略
	
	for(int u, v, i = 1; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);
	
	dfs(1, 0);
	printf("%lld\n", ans % mod);
	return 0;
}

是很经典的一类树形dp题呢。

迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值