Find the Maximum - 題解【思維,貪心】

AlexHoring發表於2022-04-18

題面

這是2022年ICPC昆明站的F題。在賽場上,我一開始敲了個貪心,但是出鍋了,改敲樹形DP,但是時間來不及了。在隊友的提醒下補過了這個題,知道解法的我發現我就是個純純的老壇……

原題連結在牛客網:F-Find the Maximum_第46屆ICPC亞洲區域賽(昆明)(正式賽),需要先登入之後才能訪問。下面搬運一下原題面:

A tree with nn vertices is a connected undirected graph with \(n\) vertices and \(n-1\) edges.

You are given a tree with \(n\) vertices. Each vertex has a value \(b_i\). Note that for any two vertices there is exactly one single path between them, whereas a simple path doesn't contain any edge more than once. The length of a simple path is considered as the number of edges in it.

You need to pick up a simple path whose length is not smaller than \(1\) and select a real number \(x\). Let \(V\) be the set of vertices in the simple path. You need to calculate the maximum of \(\frac{\sum_{u\in V}(-x^2+b_{u}x)}{|V|}\).

輸入描述

The first line contains a single integer \(n (2\le n \le 10^5)\) , indicating the number of vertices in the tree.

The second line contains \(n\) integers \(b_1,b_2,\cdots,b_n \enspace (10^{-5} \leq b_i \leq 10^{5})\) indicating the values of each vertex.

Each line in the next \(n−1\) lines contains two integers \(u,v\) indicating an edge in the tree.

輸出描述

The output contains a single real number, indicating the answer.

Your answer will be accepted if and only if the absolute error between your answer and the correct answer is not greater than \(10^{-4}\).

時空限制

時間限制:C/C++ 1秒,其他語言2秒
空間限制:C/C++ 262144K,其他語言524288K

輸入

2
3 2
1 2

輸出

1.562500

大意

給出一顆無向樹,樹上的每一個節點都有一個權值\(b_i\),要求找到一條長度不小於1的簡單路徑,記路徑上的節點的權值的算術平均值為\(t\),求\(\frac{t^2}{4}\)的最大值。

題解

本題權重可正可負,而最後需要求的式子是平均值的平方,因此在求解的時候需要同時考慮最大和最小兩種情況。而要想路徑上的節點算術平均值最大,路徑的長度只能為1或者2。

為什麼呢?假如有一條長度為3的路徑\(L:v_1-v_2-v_3-v_4\),那麼我們將其拆分成兩條路徑\(L_1:v_1-v_2,L_2:v_3-v_4\),由算術平均的性質,\(L\)包含的節點的算術平均不大於\(L_1,L_2\)包含節點的算術平均的最大值,也不小於其中的最小值。

我們來簡單說明一下:假設\(w_i\)代表編號為\(i\)的節點的權值,那麼:

$ t=\frac{w_1+w_2+w_3+w_4}{4}=\frac{w_1+w_2+w_3+w_4}{2}\times \frac{1}{2}=(\frac{w_1+w_2}{2}+\frac{w_3+w_4}{2})\times \frac{1}{2}=\frac{t_1+t_2}{2}$

也就是說\(t\)最大當且僅當\(t_1=t_2=t\),這樣我們考慮長度為1的路徑即可。同樣的,可以將其推廣到長度為\(4,5,\dots\)的路徑上,它們都可以拆分成兩條長度不小於1的路徑,權值算術平均在拆分部分算術平均的最大值與最小值之間。因此,要找最大值,只需考慮所有長度為1或者2(即包含2個或者3個節點)的路徑即可。由於權值可能為負,因此我們求出最大的正平均權值和最小的負平均權值,然後進行比較即可。

長度為1的路徑邊讀入邊處理即可,長度為2的路徑可以通過一輪DFS來尋找:若指定一個節點為DFS的起點,則可以按照各節點到起點的距離將其視作一顆有向樹,那麼長度為2的路徑就有兩種情況:

(1)當前節點、當前節點的父節點以及當前節點的子節點,路徑為父節點-當前節點-子節點

(2)當前節點和它的兩個子節點,路徑為子節點-當前節點-另一個子節點。

第一種情況可以很方便的列舉,而第二種情況,由於求的是最值,所以我們可以對所有的子節點排序,貪心地選擇權值最大的前兩個子節點和權值最小的前兩個子節點進行討論就可以了。

對於子節點的排序時間複雜度為\(O(nlogn)\),DFS遍歷全圖的複雜度為\(O(n)\),綜合下來時間複雜度為\(O(nlogn)\),在題目給出的資料範圍內可以接受。

程式碼實現如下:

#include <bits/stdc++.h>
#define GRP int T;cin>>T;rep(C,1,T)
#define FAST ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=a;i>=b;--i)
#define elif else if
#define mem(arr,val) memset(arr,val,sizeof(arr))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

int n;
double w[100010];		//權值
double ans;
vector< vector<int> > e;	//vector實現鄰接表
int u, v;

void dfs(int r, int pre) {
	//將所有的子節點按照權值排序
	sort(e[r].begin(), e[r].end(), [](int a, int b)->bool{
		return w[a] > w[b];
	});
	//如果子節點有至少兩個,就考慮子節點-當前節點-子節點這樣的路徑(以無向樹儲存,DFS只是視作有向樹,因此需要排除掉父節點)
	if (e[r].size() > 2 || (pre < 1 && e[r].size() > 1)) {
		int cnt = 0, flag = 0;
		double cur = w[r];
		//挑權值最大的兩個節點
		while (cnt != 2) {
			//如果其中一個是父節點
			if (e[r][flag] == pre) {
				++flag;
				continue;
			}
			cur += w[e[r][flag]];
			++flag;
			++cnt;
		}
		cur /= 3;
		ans = max(ans, cur * cur / 4);	//更新答案
		cnt = 0, flag = e[r].size() - 1;
		cur = w[r];
		//挑權值最小的兩個節點
		while (cnt != 2) {
			if (e[r][flag] == pre) {
				--flag;
				continue;
			}
			cur += w[e[r][flag]];
			--flag;
			++cnt;
		}
		cur /= 3;
		ans = max(ans, cur * cur / 4);
	}
	for (int i : e[r]) {
		if (i == pre) {
			continue;	//不能回頭
		}
		//不是起始節點的情況下,考慮父節點-當前節點-子節點的路徑
		if (pre > 0) {
			double t = (w[pre] + w[r] + w[i]) / 3;
			ans = max(ans, t * t / 4);	//更新答案
		}
		dfs(i, r);	//繼續搜尋
	}
}
int main() {
	FAST
	cin >> n;
	e.resize(n + 1);
	ans = 0;
	rep(i, 1, n) {
		cin >> w[i];
	}
	rep(i, 1, n - 1) {
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
		double t = (w[u] + w[v]) / 2;  //長度為1的邊讀入時就可以進行處理
		ans = max(ans, t * t / 4);
	}
	dfs(1, -1);	//指定起始節點為第一個節點,並且指定其父節點不存在
	cout << fixed << setprecision(6) << ans << endl;
	return 0;
}

/*
          _           _    _            _
    /\   | |         | |  | |          (_)
   /  \  | | _____  _| |__| | ___  _ __ _ _ __   __ _
  / /\ \ | |/ _ \ \/ /  __  |/ _ \| '__| | '_ \ / _` |
 / ____ \| |  __/>  <| |  | | (_) | |  | | | | | (_| |
/_/    \_\_|\___/_/\_\_|  |_|\___/|_|  |_|_| |_|\__, |
                                                 __/ |
                                                |___/
*/

相關文章