[CF1292C] Xenon's Attack on the Gangs

题意

题目链接

给定一棵树,试将 [ 0 , n − 2 ] [0,n-2] [0,n2] 内的每个整数不重不漏填到每条边上,使得 ∑ 1 ≤ u < v ≤ n mex ( u , v ) \sum\limits_{1\le u<v\le n} \text{mex}(u,v) 1u<vnmex(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 2n3000

题解

考虑我们选定了一条边 ( 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{ksonimaxfk,j,ksonjmaxfi,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 Nsizu,然后只更新 ∀ i ∈ T ( u ) , f fa u , i \forall i\in T(u),f_{\text{fa}_u,i} iT(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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值