hdu5290 Bombing plan(樹DP)

題意:給一棵樹,邊長都是1,每個點有個點權w[i],表示這個點能覆蓋與其距離多遠的點。選最少的點覆蓋樹上所有點。N<=10W, w[i]<=100.

樹DP好久都沒練了,並且感覺以前也不是特別擅長寫樹DP,見的模型也不多。以前的樹DP基本都是多叉轉二叉,記錄向上若干節點的信息,樹揹包等套路,這道題不一樣,因爲是無根樹,每個點不僅會輻射他的父親,還對他兒子有影響。我們要開兩個DP數組,f[i][j]表示i節點子樹全部被覆蓋,且與其距離爲j的祖先也被輻射的距離,g[i][j]表示i的子樹中未覆蓋完且未覆蓋的離i最遠的點離i的距離爲j的最小代價。以前做DP的時候非常避諱同時轉移多個值,因爲這樣可能一些最優化信息被覆蓋掉。而這裏不一樣,這兩個數組描述的是一個最優化信息,可以互相輔助轉移。以後碰到每個點輻射周圍一定距離的樹DP可以採用這種套路。

這道題還有一個重要的地方在於DP數組單調性的維護。由於一些奇怪的原因在轉移的過程中會出現價值更高的決策反而代價更小,我們用前綴或後綴最值的方法,用價值更高的決策的代價去更新價值更低的決策的代價。比如說當f[i][j]<f[i][j-1]的時候,我們完全可以直接用f[i][j]代替f[i][j-1]來轉移,因爲能多覆蓋一定比少覆蓋更優,這種情況直接用f[i][j]的值去更新f[i][j-1]即可。

這道題上所體現出來的思想非常有價值。以前談道DP的優化總是想起什麼單調隊列,斜率優化,數據結構優化之類複雜卻非常死板的東西。而這裏用了一些非常簡單的技巧就使得DP轉移的複雜度從nw^2降到了nw,確實很強。開始看課件時只是大致感受到了怎麼優化,具體實現還是參考了下別人的代碼。以後要多積累經驗。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define erp(i,a,b) for(int i=a;i>=b;--i)
#define LL long long
using namespace std;
const int inf = 0x3f3f3f3f;
const int MAXN = 100002;
int w[MAXN], N;
int f[MAXN][102], g[MAXN][102];
inline void gmin(int&a, const int&b) { a>b?a=b:0; }

struct Ed {
	int to; Ed*nxt;
} Edges[MAXN*2], *ecnt, *adj[MAXN];
void adde(int a, int b)
{
	(++ecnt)->to = b;
	ecnt->nxt = adj[a];
	adj[a] = ecnt;
}

void dfs(int u, int fa)
{
	rep(i, 0, 100) f[u][i] = inf;
	memset(g[u], 0, sizeof g[u]);
	int sum = 1, v;
	for (Ed*p = adj[u]; p; p=p->nxt)
	{
		v = p->to;
		if (v == fa) continue;
		dfs(v, u);
		if (w[u]) sum += g[v][w[u]-1];
		else sum += f[v][0];
		g[u][0] += f[v][0];
		rep(i, 1, 100) g[u][i] += g[v][i-1];
	}
	for (Ed*p = adj[u]; p; p=p->nxt)
	{
		v = p->to;
		if (v == fa) continue;
		gmin(f[u][0], f[v][1] + g[u][0] - f[v][0]);
		rep(i, 1, 99) gmin(f[u][i], f[v][i+1] + g[u][i] - g[v][i-1]);
	}
	rep(i, 0, w[u]) gmin(f[u][i], sum);
	erp(i, 99, 0) gmin(f[u][i], f[u][i+1]);
	gmin(g[u][0], f[u][0]);
	rep(i, 1, 100) gmin(g[u][i], g[u][i-1]);
}

int main()
{
	int u, v;
	while (~scanf("%d", &N))
	{
		rep(i, 1, N) adj[i] = 0;
		ecnt = Edges;
		rep(i, 1, N) scanf("%d", w+i);
		rep(i, 1, N-1)
		{
			scanf("%d%d", &u, &v);
			adde(u, v), adde(v, u);
		}
		dfs(1, 0);
		printf("%d\n", f[1][0]);
	}
	return 0;
}
發佈了98 篇原創文章 · 獲贊 26 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章