Codeforces 1292C Xenon's Attack on the Gangs


Description

描述

給一個 $n$ 個點的樹,要求你將 $0 \sim n - 2$ 不重不漏的放在這 $n - 1$ 條邊上,求 $S = \sum\limits_{1 \le u < v \le n} \operatorname{mex}(u, v)$ 的最大值,$\operatorname{mex}(u, v)$ 表示 $<\! u \to v \!>$ 的路徑上所經過的邊權集合中最小的沒出現的非負數。

輸入

第一行一個正整數 $n$($2 \le n \le 3000$)。

接下來 $n - 1$ 行,每行兩個數 $u, v$,表示一條邊($1 \le u, v \le n$,$u \neq v$)。

輸出

一個數 $S$ 表示答案。

樣例

輸入1

3
1 2
2 3

輸出1

3

輸入2

5
1 2
1 3
1 4
3 5

輸出2

10

解釋

樣例1:

  • $\operatorname{mex}(1,2)=0$
  • $\operatorname{mex}(1,3)=2$
  • $\operatorname{mex}(2,3)=1$

所以 $S = 0 + 2 + 1 = 3$。

樣例2:

  • $\operatorname{mex}(1, 3) = 1$
  • $\operatorname{mex}(1, 5) = 2$
  • $\operatorname{mex}(2, 3) = 1$
  • $\operatorname{mex}(2, 5) = 2$
  • $\operatorname{mex}(3, 4) = 1$
  • $\operatorname{mex}(4, 5) = 3$

所以 $S = 1 + 2 + 1 + 2 + 1 + 3 = 10$。

Solution

觀察一下答案式子:

$$ \large
\begin{array}{rl}
S = & \!\! \sum\limits_{1\le u<v \le n} \operatorname{mex}(u, v) \\
= & \!\! \sum\limits_{x = 1}^{n} \left( \sum\limits_{\operatorname{mex}(u, v) = x} x \right) \cdots\cdots(1)\\
= & \!\! \sum\limits_{x = 1}^{n} \left( \sum\limits_{\operatorname{mex}(u, v) \ge x} 1 \right)\cdots\cdots(2)
\end{array}$$

$(1) \Rightarrow (2)$ 是怎么推的呢?

考慮一個 $\operatorname{mex}(u, v)$,比如它等於 $y$,原本它對答案只產生一次為 $y$ 的貢獻;現在,它對於 $\forall 1\le x\le y$ 都會產生 $1$ 的貢獻,正好和還是 $y$。

於是我們定義一個函數 $\operatorname{F}()$,$\operatorname{F}(x) = \sum_{1 \le u < v \le n}[\operatorname{mex}(u, v) \ge x]$。那么答案式子又可以化為:

$$ \large \begin{array}{rl} S = & \!\! \sum\limits_{x = 1}^{n} \left( \sum\limits_{\operatorname{mex}(u, v) \ge x} 1 \right)\cdots\cdots(2) \\
= & \!\! \sum\limits_{x = 1}^{n} \operatorname{F}(x)
\end{array} $$

$\operatorname{F}(x)$ 說通俗一點,就是 $< \! u \to v \! >$ 至少包含了 $0 \sim x - 1$ 的所有數的路徑數量。

現在我們從 $0$ 開始,依次放每一個數。比如現在我們要放 $x$,$0 \sim x - 1$ 已經放好了,那么我們一定會把 $x$ 和它們放在同一條路徑上,要不然 $x$ 放了以后對答案沒有影響。這是因為,放在同一條路徑上的話,這里的 $\operatorname{mex}$ 就要變成 $x+ 1$ 了;否則 $0 \sim x - 1$ 已經有數字缺失,那就保持那個缺失的最小數字不變。

那么我們就可以找一個 $<\!u \to v\!>$,它的長度為 $l$,我們要把 $0 \sim l - 1$ 都放在這一條路上。$l \sim n - 2$ 放的位置我們不管,因為它們不會使得答案變劣,更優的方法在后面也一定能枚舉到。

想一想,一個最佳的方案一定不僅是完整的一段,而且它的一部分也要是完整的。

比如我們現在選了 $<\!u \to v\!>$:

然后 $0$ 我們隨便放一下:

下面我們要放 $1$,為了利益最大化,顯然,$1$ 和 $0$ 要在一起(這樣我們在構造 $0 \sim l - 1$ 的同時也順便構造了一個 $0 \sim 1$ 的路徑)。

比如 $1$ 放在左邊:

然后我們放 $2$,這時我們不管放在 $1$ 的左邊還是 $0$ 的右邊,都可以順便構成 $0 \sim 2$ 的路徑。

依次類推放完:

發現了什么?從 $u$ 到 $v$ 依次寫下來,正好是一個 單谷序列,也就是比如把這個序列叫做 $a$,則有一個位置 $p$,使得 $a_1 > a_2 > \cdots > a_p < \cdots < a_{l-1} <a_l$。

  • 用 $dp(u, v)$ 表示把 $0 \sim l - 1$ 放在 $<\!u \to v\!>$ 上,$\sum_{i =1}^{l} \operatorname{F}(i)$ 的最大可能值;
  • 用 $s_{root, u}$ 表示以 $root$ 為根時,$u$ 為根的子樹大小;
  • 用 $p_{root, u}$ 表示以 $root$ 為根時,$u$ 的父親。

現在我們要計算 $dp(u, v)$,根據單谷序列的性質,$l - 1$ 要么在最左邊,要么在最右邊,那我們分類討論一下:

  • 如果 $l - 1$ 放在最左邊,那么剩下的部分就是 $dp(p_{v, u}, v)$ 的答案,而享受到 $0 \sim l - 1$ 的路徑的個數就是 $\operatorname{F}(l) = s_{u, v} \times s_{v,u}$,所以 $dp(u, v) = dp(p_{v, u}, v) + s_{u, v} \times s_{v,u}$;
  • 如果 $l - 1$ 放在最右邊,那么剩下的部分就是 $dp(u, p_{u, v})$ 的答案,而享受到 $0 \sim l - 1$ 的路徑的個數依然是 $\operatorname{F}(l) = s_{u, v} \times s_{v,u}$,所以 $dp(u, v) = dp(u, p_{u, v}) + s_{u, v} \times s_{v,u}$。

綜上所述:
$$\large dp(u, v) = \max(dp(u, p_{u, v}), dp(p_{v, u}, v)) + s_{u, v} \times s_{v,u} $$

我們可以記憶化搜索,$dp(u, v)$ 就可以 $\mathcal O(1)$ 求了,總時間復雜度就是枚舉 $u, v$ 的 $\mathcal O(n^2)$。至於 $p_{root, u}$ 和 $s_{root, u}$,可以在之前通過枚舉 $root$,每次 $\mathcal O(n)$ 預處理出來。總時間復雜度 $\mathcal O(n^2)$。

代碼貼出,僅供參考:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3005;
int n, rt, p[N][N], s[N][N];
LL f[N][N], ans;
vector<int> G[N];
void build(int u)
{
	s[rt][u] = 1;
	for(int v : G[u]) if(v ^ p[rt][u])
	{
		p[rt][v] = u;
		build(v);
		s[rt][u] += s[rt][v];
	}
}
LL dp(int u, int v)
{
	if(u == v) return 0;
	if(f[u][v]) return f[u][v];
	return f[u][v] = max(dp(u, p[u][v]), dp(v, p[v][u])) + s[u][v] * s[v][u];
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	for(int i = 1; i < n; i++)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	for(int i = 1; i <= n; i++) { rt = i; build(i); }
	for(int u = 1; u <= n; u++) 
        for(int v = 1; v <= n; v++)
            ans = max(ans, dp(u, v));
	cout << ans << endl;
	return 0;
}

參考文獻:
Akikaze.Codeforces Round #614 Editorial.CodeForces


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM