【Codeforces Round #614(div2)】E-Xenon's Attack on the Gangs(树形dp)

一、题目链接

https://codeforces.com/contest/1293/problem/E

二、题意

给n个结点,n-1条无向边。即一棵树。我们需要给这n-1条边赋上0~n-2不重复的值。

mex(u,v)表示从结点u到结点v经过的边权值中没有出现的最大非负整数。

S定义如下:
在这里插入图片描述

求S的最大值。

三、数据范围

2 ≤ n ≤ 3000

1 ≤ u i u_i , v i v_i ≤ n; u i u_i v i v_i

四、解题思路

首先结合样例二理解题意。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
譬如mex(2,5),经过的边权为3、0、1,没出现过的最小非负整数为2,故mex(2,5)=2。

我们发现很关键的一点:样例解释中省略了mex(1,4)、mex(1,2)、mex(3,5),因为其值都为0,对S无贡献。因此,一条路径是否对S有贡献,取决于它是否经过0;它作出多大的贡献,取决于它经过的最长连续非负整数的个数。经过0、1,贡献2;经过0、1、2,贡献3……

换句话说,可以这样计算S:经过边权0的路径,贡献+1;经过边权0、1的路径,贡献再+1……要使S最大,这些连续非负整数所在边要尽可能连续。

理解了这一点后,我们要作赋值规划,不妨从“0”开始考虑。权值为“0”的边的两边结点数的乘积就是“0”对这棵树的贡献;权值为“0、1”的路径两边结点数的乘积就是“1”对这棵树的贡献。

我们用这个思路重新计算样例:“0”左边有三个结点(1、2、4),右边有两个结点(3、5),贡献3×2=6;

“0、1”左边有三个结点(1、2、4),右边有一个结点(5),贡献3×1=3;

“0、1、2”左边有一个结点(4),右边有一个结点(5),贡献1×1=1;

“0、1、2、3”在图中不连续,贡献0。

sum = 6 + 3 + 1 = 10 = S,符合预期。

从中可以看出,S的值是由更小区间获取的状态的转移、路径两边的结点数共同决定。我们从树中抽出一条首端为i,末端为j的链进行思考,则有:

S = dp[i][j] = i左边的结点数 * j右边的结点数 + max(dp[i,j-1],dp[i+1,j])

我们把树拆解成链。由于不连续的边权对S毫无贡献,所以每次对链进行计算时,其他结点都可以忽略。枚举链,进行动态规划,取最大值,即为ans。

五、AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 3005;
vector <int> edge[MAXN];
int n, fa[MAXN][MAXN];
ll ans, cnt[MAXN][MAXN], dp[MAXN][MAXN];

ll solve(int a, int b)
{
    if (a == b) return 0;
    if (dp[a][b] != -1) return dp[a][b];
    return dp[a][b] = cnt[a][b] * cnt[b][a] + max(solve(fa[a][b], a), solve(fa[b][a], b)); //状态转移,fa[a][b]即为从b结点向a结点靠近一点,fa[b][a]同理
}
void search(int root, int k, int dad)
    //dad是以root为根结点情况下,k的父亲
{
    cnt[root][k]++; //以root为根,k子结点的个数。这里是先把k结点计算进去,下面再枚举更新
    fa[root][k] = dad; //更新k的父结点
    for (auto t:edge[k]) //枚举和k相连的边
    {
        if (t != dad) //确保是子结点
        {
            search(root, t, k); //k变成父结点,更新子结点t的状态
            cnt[root][k] += cnt[root][t]; //更新以root为根,k子结点的个数
        }
    }
}
int main()
{
    cin >> n;
    memset(dp, -1, sizeof(dp));
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v); //edge记录两个结点的连接情况
        edge[v].push_back(u); //双向赋值
    }
    for (int i = 1; i <= n; i++)
        search(i, i, -1); 
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i != j) ans = max(ans, solve(i, j));
    cout << ans;
    return 0;
}

这学期做的第一道树形DP,图论真是太有意思啦!!!得抓紧最后的假期多看看,呜呜呜~过后又要回到商学院的经济学世界里了~

发布了5 篇原创文章 · 获赞 8 · 访问量 704

猜你喜欢

转载自blog.csdn.net/weixin_45785492/article/details/104102611
今日推荐