hihocoder Tower Defense Game(樹上貪心)

題目大意

給定一顆以1爲根節點的樹,每個節點有一個購入價格p和賣出價格q

進入一個節點時需要花費p,離開時可以收回q,每個節點只產生一次購入和賣出。

請你選擇一個遍歷的順序,要求在遍歷的過程中身上的錢數不小於0,且出發時帶的錢數最少。

按照遍歷的順序是指:當你選擇了一顆子樹之後,你需要將這個子樹全部走完,才能選擇其他子樹

解題思路

該題爲一道樹形圖上的貪心問題。

我們每一步的決策在於:當遍歷到一個根時,對於其擁有的若干顆子樹,應該以怎樣的順序來遍歷這些子樹

先將問題簡化爲二叉樹,並且兩顆子樹都只有根節點:

首先我們選擇左邊的路徑,則需要攜帶的錢數爲:

經過1: L1 = p1

經過2: L2 = p1-q1+p2

經過3: L3 = p1-q1+p2-q2+p3

然後考慮走右邊的情況:

經過1: R1 = p1

經過3: R2 = p1-q1+p3

經過2: R3 = p1-q1+p3-q3+p2

對於這6個值,我們要求的是min( max(L1, L2, L3), max(R1, R2, R3) )

由於在題目中已經明確說明總是有p≥q,可以肯定有L3≥R2,R3≥L2,所以可以直接將L2R2排除。

又因爲1是必經之路,因此在選擇左右時,實際上需要比較的只有L3R3

我們將L3R3換一種形式表示:pSum = p1+p2+p3, qSum = q1+q2+q3,則L3=pSum-qSum+q3,R3=pSum-qSum+q2

L3>R3,則有q3>q2,此時要選擇的是R3,即走右路;

L3<R3,則有q2>q3,此時要選擇的是L3,即走左路。

由於pSumqSum的值是確定的,因此實際上我們只需要根據q2q3的值就可以選擇走哪邊。

最後的結論也就是:對於二叉樹的情況,我們選擇q值大的一邊先走,一定能保證結果值最小


那麼接下來將這個結論推廣至多叉樹的情況:

對於多叉樹的情況,我們將子樹按照q值從大到小的順序,一定能保證結果值最小

這是由於q值之間的大小順序是絕對的,假設3個子節點分別爲A,B,C,若qA≥qB,qB≥qC,則一定有qA≥qC

不妨舉個例子:

根據q值,對應的6種遍歷順序分別爲:

1,2,3:需要帶錢8
1,3,2:需要帶錢7
2,1,3:需要帶錢8
2,3,1:需要帶錢7
3,1,2:需要帶錢7
3,2,1:需要帶錢6


那麼還有一個問題,一個節點的pq是直接給定的,我們應該如何來求一顆子樹的ptqt(爲了便於區分,我們將樹的pq記爲ptqt)?

在計算過程中,我們採用遞歸的從根節點開始遍歷。在計算當前節點時,我們需要將其子樹的pq都先計算出來。

因此在計算當前子樹時,我們已經知道了根節點的pq,以及每顆子樹的ptqt(記作pt1,pt2,pt3,...qt1,qt2,qt3,...,並且滿足qt1≥qt2≥qt3≥...)。

在該題中所有節點的總購買錢數不會超過200,000,000,不妨設置一個初始錢數wallet = 200000000

同時我們還需要一個值minWallet,來記錄錢包錢數最少時的值,初始也爲200,000,000。

首先處理根節點:

wallet = wallet - p
If (minWallet > wallet) Then
    minWallet = wallet
End If
wallet = wallet + q

接下來處理每個子樹:

For i = 1 .. chdNumber
    wallet = wallet - pt[i]
    If (minWallet > wallet) Then
        minWallet = wallet
    End If
    wallet = wallet + qt[i]
End For

最後可以得到整個樹的pq分別爲:

pt[root] = 200000000 - minWallet // 遍歷這顆樹至少需要的錢數
qt[root] = wallet - minWallet    // 最後的錢數-花費的錢數=返還的錢數

完整的僞代碼爲:

DFS(rt):
    wallet = 200000000
    minWallet = 200000000
    // 先遞歸處理每個子樹
    For each child i of rt
        DFS(i)
    End For
    // 對該根節點的所有子樹按照qt值排序
    sortByQt(children)
    // 處理根節點
    wallet = wallet - p[rt]
    If (minWallet > wallet) Then
        minWallet = wallet
    End If
    wallet = wallet + q[rt]
    // 處理子樹
    For each child i of rt
        wallet = wallet - pt[i]
        If (minWallet > wallet) Then
            minWallet = wallet
        End If
        wallet = wallet + qt[i]
    End For
    // 記錄子樹
    pt[rt] = 200000000 - minWallet
    qt[rt] = wallet    - minWallet

最後所求的結果是pt[1]

由於在每一顆子樹都使用了排序,最後這個算法的時間複雜度爲O(NlogN),即使在N爲10000的數據下,也能夠順理通過。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn = 10005;
const int inf = 200000000;
struct Node
{
	vector<int> child;
}tree[maxn];
int n,p[maxn],q[maxn];
int pt[maxn],qt[maxn];

bool cmp(int a,int b)
{
	return qt[a] > qt[b];
}

void dfs(int u,int fa)
{
	int Wallet = inf;
	int minWallet = inf;
	vector<int> V = tree[u].child;
	for(int i = 0; i < V.size(); i++)
	{
		int v = V[i];
		if(v == fa) continue;
		dfs(v,u);
	}
	sort(V.begin(),V.end(),cmp);
	Wallet = Wallet - p[u];
	if(minWallet > Wallet)
		minWallet = Wallet;
	Wallet = Wallet + q[u];
	for(int i = 0; i < V.size(); i++)
	{
		if(V[i] == fa) continue;
		Wallet = Wallet - pt[V[i]];
		if(minWallet > Wallet)
			minWallet = Wallet;
		Wallet = Wallet + qt[V[i]];
	}
	pt[u] = inf - minWallet;
	qt[u] = Wallet - minWallet;
}

int main()
{
	int u,v;
	while(scanf("%d",&n)!=EOF)
	{
		for(int i = 1; i <= n; i++)
			scanf("%d%d",&p[i],&q[i]);
		for(int i = 1; i <= n; i++) tree[i].child.clear();
		for(int i = 1; i < n; i++)
		{
			scanf("%d%d",&u,&v);
			tree[u].child.push_back(v);
			tree[v].child.push_back(u);
		}
		dfs(1,0);
		printf("%d\n",pt[1]);
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章