炸弹
题目描述:
一个树,n~1e5个点,每个点有一个权值,表示可以如果炸掉它,就可以免费炸掉距离他小于等于w[i]的点.每个点的权值<=100; 问最少炸几个可以全部炸完.
题解:
首先是一个点有一定范围的树上的dp.以前做过的是可以开二维dp[i][j],i是被j点控制的,j不限于i的子树. 但是这道题不能开二维,但是因为权值小于等于100,所以 用g[i][j]表示i为根的子树,从外面获取了炸到i的fa j的长度的权利. i点是1,i的儿子是2…. 这样j=0就是没有外界提供. 考虑转移g[i][j]:对于儿子v, 并不是单纯的g[v][j-1],而是对于v来说,从外界获取的小于等于j-1的g, 以及v可以向外面扩充的所有的j.(因为并不一定缺恰好i-1最好,我们先定义的都是恰好,可能反倒向外扩充一个东西更好). 所以我们要额外维护一个f[u][i],表示的是可以向外扩充i的距离的最小值.这样我们再根据实际重新定义下:f[u][i]指u点向外扩充大于等于i的最小值,g[u][i]表示u点从fa需要获取的小于等于i的长度的最小值,可能也往外放,(只要小于等于获取的值就行). 这样怎么转移? f[u][i]是横叉dp过去,强制当前的是要提供i的,其他的都用g[i]就行. g[u][i]每一个点都用g[v][i-1]. 然后重点来了,我们这样算出来的是恰好值,需要每一个点算完之后 都弄成实际意义的小于等于的值.
重点:
(1)树上一个点有控制范围.多开一维描述范围.
(2)g不够用,想明白恰好和满足限制下最小值的区别,额外增加f
(3)f和g的转移,之后再取最值.
代码:
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(int i = a;i < b;i++)
#define REP_D(i, a, b) for(int i = a;i <= b;i++)
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 10;
const int maxDist = 100+10;
const int INF = 1e8+100;
int n, w[maxn];
vector<int> G[maxn];
int f[maxn][maxDist], g[maxn][maxDist];
void dfs(int u, int fa)
{
REP(i, 0, G[u].size())
{
int v = G[u][i];
if(v!=fa)
{
dfs(v, u);
}
}
for(int i = 0;i<=100;i++)
{
f[u][i] = INF;
int a, b;
if(w[u]>=i)
{
b = 1;
}
else
{
b = INF;
}
a = 0;
REP(j, 0, G[u].size())
{
int v = G[u][j];
if(v!=fa)
{
int t = f[v][i+1];//简单的横叉转移
//int t_min = min(m_g[v][i], m_f[v][i+1]);
if(t!=INF)
{
b = min(b+g[v][i] , a+t);
}
else
{
b = b + g[v][i];
}
a += g[v][i];
}
}
f[u][i] = b;
g[u][i] = INF;//g的转移也很简单
if(i>=1)
{
a = 0;
}
else
{
a = 1;
}
REP(j, 0, G[u].size())
{
int v = G[u][j];
if(v!=fa)
{
int limit = max(0, i-1);
int t = g[v][limit];
//t = min(t,m_f[v][100]);
a += t;
}
}
g[u][i] = a;
}
f[u][101] = INF;
g[u][101] = INF;
for(int i = 100;i>=0;i--)//关键理解:我们定义的f和g有实际意义.
{
f[u][i] = min(f[u][i], f[u][i+1]);
}
g[u][0] = min(g[u][0], f[u][0]);
for(int i = 1;i<=100;i++)
{
g[u][i] = min(g[u][i], g[u][i-1]);
}
// for(int i = 0;i<=3;i++)
// {
// printf("u is %d i is %d f is %d g is %d\n", u, i, f[u][i], g[u][i]);
// }
}
void solve()
{
dfs(1, 0);
printf("%d\n", g[1][0]);
}
int main()
{
freopen("3Cin.txt", "r", stdin);
//freopen("3Cout.txt", "w", stdout);
while(scanf("%d", &n) != EOF)
{
REP_D(i, 1, n)
{
scanf("%d", &w[i]);
}
REP_D(i,1,n)
{
G[i].clear();
}
REP_D(i, 1, n-1)
{
int a, b;
scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
solve();
}
return 0;
}