CF1280D 题解

这道题是个简单题,但是我太菜了。

根据数据范围,发现是个 $O(n^2)$ 的题。再一看,直接贪心似乎不行,因为你考虑假设现在断开了一个联通块,并且它是好的,但是假设把这个联通快再多加入一些节点,它还是好的,那么它可能让答案更优,因为当前已经选的联通快变少了,那么剩下可以分的联通块数也变多了,所以其它节点可能能创造更多好的联通快。那么不是贪心,也找不到什么特别好的性质,并且是一类最优解问题,不难想到动规。

我们再一思考,这道题是在树上,树上 $n^2$ 的动规有哪几种可能?

  1. 其一,最简单的,一维状态,由子节点或者父节点转移过去。但是显然你想不太到这么简单的方案;
  2. 二维的状态,那第二维该弄个什么好呢?发现好像只能是块的个数。但是发现这样就变成树上背包了,也就是下面那种。
  3. 那就是树上背包。

    我们来具体讲一下怎么树上背包。首先状态不说,肯定是 $f[u][i]$ 表示以 $u$ 这个节点为根的子树分成 $i$ 块,不算 $u$ 所处的那一块的最大值,然后我们称一个点的权值为 $a_i$ ,其等于 $w_i - b_i$ 。然后我们经思考后,发现好像不能直接去树上背包,原因是你还是不知道当前根节点 $u$ 这一块的权值是多少。

    当时我就愣住了,之后便认为这道题恐怖如斯,不是我能做出来的题。但这时候就应该去找一些性质或者贪心策略以使这个问题可以迎刃而解。所以我们找到了一个非常漂亮的性质(贪心策略):我们思考两种方案,第一种的块数比第二种多,但是第二种 $u$ 那个联通块的权值和大于第一种。

为了防止搞混两种,再清晰地理一下:

  1. 块数多
  2. $u$ 那个联通块的权值大

第一种一定 不劣于 第二种!

我们采取反证法,思考假设第二种比第一种优会优再什么情况下:可能存在第二种他的权值很大,然后把上边权值是非正数的一块变成正数了,然后会使答案加 $1$ ,那么我们思考第一种,他可以把权值是非正数的那块不去管他,那么他在最差情况下也就是第二种在最好情况下会和第二种一样。那么什么情况下第一种会比第二种优呢?就比如说 $u$ 上面的所有节点权值都是正数,那么第二种就会比第一种要差。

那么假设有两种方案块数相等,不难想到u那块必定是权值越大越好,因为可能越大。那么我们再用一个 $g[u][i]$ 表示在块数为 $f[u][i]$ 时 $u$ 那个联通块的最大权值。

最后给转移方程(这里用的是刷表法):

  1. $v$ 所处的联通块不和 $u$ 合并,即单独划分

    用 $f_{u,i}+f_{v,j}+(g_{v,j}>0),g_{u,i}$ 去更新 $f_{u,i+j},g_{u,i+j}$ 的最优值。

  2. $v$ 的那个联通块与 $u$ 合并

    用 $f_{u,i}+f_{v,j},g_{u,i}+g_{v,j}$ 去更新 $f_{u,i+j-1},g_{u,i+j-1}$ 的最优值。

边界是 $f[u][1]=0,g[u][1]=a_u$ 。

树上背包的时间复杂度 $O(n^2)$ 的证明见我的这篇博客

其实这跑得过去是必然的,因为我们考虑最坏情况, $m$ 全是 $3000$ ,由于一句很重要的话: $n$ 之和不超过 $10^5$ ,所以我们发现,利用乘法分配律,最大可能是 $3*10^8$ 。而时限不紧,有整整 $4$ 秒,且动态规划常数算很小所以就这么跑过去了。

#include <bits/stdc++.h>
#define ll long long
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 3010;
int T, n, m, a[N], b[N], w[N], sz[N];
int f[N][N], r[N];
ll g[N][N], c[N];
vector<int>e[N];
void Init(){
    memset(f, 0, sizeof(f));
    memset(g, 0xf3, sizeof(g));
    L(i, 1, n) e[i].clear();
}
void Dfs(int u, int pa){
    f[u][1] = 0, g[u][1] = a[u];
    sz[u] = 1;
    for(int v: e[u]){
        if(v == pa) continue;
        Dfs(v, u);
        L(i, 1, min(m, sz[u] + sz[v]))
            r[i] = 0, c[i] = -3e12;
        L(i, 1, min(m, sz[u])){
            L(j, 1, min(m, sz[v])){
                ll ff = f[u][i] + f[v][j] + (g[v][j] > 0), gg = g[u][i];
                if(i + j <= m && ff > r[i + j] || ff == r[i + j] && gg > c[i + j])
                    r[i + j] = ff, c[i + j] = gg;
                ff = f[u][i] + f[v][j], gg = g[u][i] + g[v][j];
                if(i + j - 1 <= m && ff > r[i + j - 1] || ff == r[i + j - 1] && gg > c[i + j - 1])
                    r[i + j - 1] = ff, c[i + j - 1] = gg;
            }
        }
        L(i, 1, min(m, sz[u] + sz[v]))
            f[u][i] = r[i], g[u][i] = c[i];
        sz[u] += sz[v];
    }
}
void Solve(){
    scanf("%d%d", &n, &m); Init();
    L(i, 1, n) scanf("%d", &b[i]);
    L(i, 1, n) scanf("%d", &w[i]);
    L(i, 1, n) a[i] = w[i] - b[i];
    int u, v;
    L(i, 1, n - 1){
        scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }
    Dfs(1, 0);
    printf("%d\n", f[1][m] + (g[1][m] > 0));
} 
int main(){
    scanf("%d", &T);
    while(T--) Solve();
    return 0;
}
posted @ 2023-01-19 12:32  徐子洋  阅读(15)  评论(0)    收藏  举报  来源