题意
给定一棵树,试将 [ 0 , n − 2 ] [0,n-2] [0,n−2] 内的每个整数不重不漏填到每条边上,使得 ∑ 1 ≤ u < v ≤ n mex ( u , v ) \sum\limits_{1\le u<v\le n} \text{mex}(u,v) 1≤u<v≤n∑mex(u,v) 最小,其中 mex ( u , v ) \text{mex}(u,v) mex(u,v) 表示从节点 u u u 到节点 v v v 经过的边集合 A u , v A_{u,v} Au,v 中最小的未出现的非负整数。
数据范围: 2 ≤ n ≤ 3000 2\le n\le 3000 2≤n≤3000
题解
考虑我们选定了一条边 ( u , v ) (u,v) (u,v) 是 0,删掉这条边后树变成两部分,则答案加上两部分的大小乘积对吧。
然后我们考虑选定一条边是 1,理性分析一下可以发现应该放在 u u u 或 v v v 的邻边上,不然同时经过 0,1 的路径肯定会少,对答案能贡献 2 的路径也会变少,并且接下来填 2 能获得的贡献也不会多 …
如图,总之我们一定会将 1 填在 0 旁边,然后就会对答案加上 A × B A\times B A×B,这样算是因为端点分别在 A , B A,B A,B 中的已经被算过一次贡献了。
然后我们会继续将 2 填在这条路径(0,1构成的路径)的端点,以此类推 … 是“沿一条路径向外扩散的”。
因此我们就转化成了个这样的问题:首先定义 u u u 左边的 siz \text{siz} siz 为以 u u u 为根的, v v v 右边的 siz \text{siz} siz 为以 v v v 为根的,选定一条边 ( u , v ) (u,v) (u,v),填 0,对答案贡献 siz u × siz v \text{siz}_u\times \text{siz}_v sizu×sizv,然后每次选择 u u u 或 v v v,比如 u u u,将其走到任意一个儿子上,然后对答案贡献 siz u × siz v \text{siz}_u\times \text{siz}_v sizu×sizv,如此一直操作直到不能操作,答案的最大值。
于是我们有了一个 O ( n 3 ) O(n^3) O(n3) 的想法:枚举边 ( u , v ) (u,v) (u,v),设 f i , j f_{i,j} fi,j 为 u u u 操作到 i i i, v v v 操作到 j j j,继续向下操作能得到的最大收益,可得如下转移方程:
f i , j = siz i × siz j + max { max k ∈ son i f k , j , max k ∈ son j f i , k } f_{i,j}=\text{siz}_i\times \text{siz}_j+\max\{ \max_{k\in \text{son}_i} f_{k,j}, \max_{k\in \text{son}_j} f_{i,k} \} fi,j=sizi×sizj+max{k∈sonimaxfk,j,k∈sonjmaxfi,k}
那么我们思考如何将其优化成 O ( n 2 ) O(n^2) O(n2),这个算法枚举 0 的复杂度应该省不了,考虑 dp 的复杂度,每次 O ( n 2 ) O(n^2) O(n2) dp 确实很慢,这是因为换一条边后点的 siz \text{siz} siz 会变。
但实际上很多点的 siz \text{siz} siz 都没变!如果我们一开始让 siz \text{siz} siz 定义为以 1 为根的,当我们选定点 ( fa u , u ) (\text{fa}_u,u) (fau,u) 时,在下图中只有红色部分的 siz \text{siz} siz 变了:
因此,我们深搜寻找答案,搜到 u u u 时处理 ( fa u , u ) (\text{fa}_u,u) (fau,u) 这条边为 0 的答案,我们将 fa u \text{fa}_u fau 的 siz \text{siz} siz 变为 N − siz u N-\text{siz}_u N−sizu,然后只更新 ∀ i ∈ T ( u ) , f fa u , i \forall i\in T(u),f_{\text{fa}_u,i} ∀i∈T(u),ffau,i,最后将 f fa u , u f_{\text{fa}_u,u} ffau,u 作为答案。
时间复杂度 O ( n 2 ) O(n^2) O(n2)。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll read() {
ll ret = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
return ret;
}
const int MAXN = 3005;
int N;
struct node { int v, next; } E[MAXN << 1]; int head[MAXN], Elen;
void add(int u, int v) { ++Elen, E[Elen].v = v, E[Elen].next = head[u], head[u] = Elen; }
ll siz[MAXN], Siz[MAXN], Dp[MAXN][MAXN]; int fa[MAXN], dfn[MAXN], dfncnt, redfn[MAXN];
void dfs(int u, int ff) {
siz[u] = 1, fa[u] = ff, dfn[u] = ++dfncnt, redfn[dfncnt] = u;
for (int i = head[u]; i; i = E[i].next) if (E[i].v != ff) dfs(E[i].v, u), siz[u] += siz[E[i].v];
}
ll getdp(ll dp[][MAXN], int u, int v, int del = 0, int add = 0) {
if (dp[u][v]) return dp[u][v];
int i; ll ret = 0;
for (i = head[u]; i; i = E[i].next) if (E[i].v != fa[u] && E[i].v != del) ret = max(ret, getdp(dp, E[i].v, v, del, add));
if (add) ret = max(ret, getdp(dp, add, v, del, add));
for (i = head[v]; i; i = E[i].next) if (E[i].v != fa[v]) ret = max(ret, getdp(dp, u, E[i].v, del, add));
dp[u][v] = siz[u] * siz[v] + ret; return dp[u][v];
}
ll f[MAXN][MAXN], ans;
void dfs2(int u, int ff) {
int i;
if (u != 1) {
for (i = dfn[u]; i <= dfn[u] + siz[u] - 1; ++i) f[fa[u]][redfn[i]] = 0;
siz[fa[u]] = N - Siz[u];
for (i = dfn[u]; i <= dfn[u] + siz[u] - 1; ++i) getdp(f, fa[u], redfn[i], u, fa[fa[u]]);
ans = max(ans, f[fa[u]][u]);
for (i = head[u]; i; i = E[i].next) if (E[i].v != ff) dfs2(E[i].v, u);
for (i = dfn[u]; i <= dfn[u] + siz[u] - 1; ++i) f[fa[u]][redfn[i]] = Dp[fa[u]][redfn[i]];
siz[fa[u]] = Siz[fa[u]];
} else {
for (i = head[u]; i; i = E[i].next) if (E[i].v != ff) dfs2(E[i].v, u);
}
}
int main() {
scanf("%d", &N); int i, j, u, v;
for (i = 1; i < N; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u);
dfs(1, 0);
for (i = 1; i <= N; ++i) Siz[i] = siz[i];
for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) f[i][j] = getdp(Dp, i, j);
dfs2(1, 0);
printf("%lld\n", ans);
return 0;
}