专栏文章

Luogu P8258 [CTS2022] 独立集问题

P8258题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miozwj7o
此快照首次捕获于
2025/12/03 03:53
3 个月前
此快照最后确认于
2025/12/03 03:53
3 个月前
查看原文
首先能够发现,如果操作了一个点 uu,那么能让新的 aua_uvEuavau\sum\limits_{v\in E_u}a_v - a_u,也能是 auvEuava_u - \sum\limits_{v\in E_u}a_v。这是因为在操作一次后 vEuv\in E_u 都有 av=0a_v = 0,此时再操作一次 uu 就相当于是取反了。
直接考虑过程太难考虑,根据合并贡献的式子可以知道最后的答案一定形如 i=1ndiai(ai{1,+1})\sum\limits_{i = 1}^n d_i a_i(a_i \in \{-1, +1\})
于是尝试判定一个 aa 数组能否被得到。
首先如果有一个点 uu 满足 vEu,auav\forall v\in E_u, a_u\not = a_v,那么肯定是可以在 uu 这里合并一次的,虽然此时并不能确定是否能这样操作,不过可以先进行尝试。
接下来会继续选一个其他的点进行合并,这个点可能会合并上一些还没有合并过的点,但也有可能合并上一个已经合并过的点。
对于还没有合并过的点,肯定要遵循上述的条件:邻域相等,与中心相反,当然如果已经被合并了就不管了。
对于已经合并过的点,也要关心这个点的符号,不过会发现如果符号对得上那可以直接合并,否则可以在这个点刚合并出来的时候就先反转符号。
于是会发现在合并的时候,已合并的点可以自行调整出符号,唯一的限制就是还没有合并过的点需要满足“邻域相等,与中心相反”的条件。
那么就可以发现每次选一个可以合并的点合并,能合并完就是 aa 数组合法的充要条件。
于是就有了 O(2nn)\mathcal{O}(2^n n) 的做法,记录 fsf_{s} 代表 ss 二进制下对应的点已经合并过了的贡献最大值,转移就是每次选择一个点 xx 并在此合并。
C
    for (int s = 0; s < (1 << n); s++) {
        for (int x = 0; x < n; x++) {
            f[s | (1 << x) | G[x]] = std::max(f[s | (1 << x) | G[x]], f[s] + std::abs(sum[G[x] & (~ s)] - sum[(1 << x) & (~ s)]));
        }
    }
接下来考虑优化。
刚刚的 dp 其实根本没有用到树的性质,在任意图上都是可行的。
树意味着 uu 的贡献只可能给到 u,v(vEu)u, v(v\in E_u),那么在定根后,就只需要考虑 u,fa(u),v(vsonu)u, fa(u), v(v\in son_u) 这些点。
于是考虑设计一个树形 dp:
  • fu,0/1f_{u, 0 / 1} 代表 uu 的贡献给到了 fa(u)fa(u)uu 的子树内的最大权值。
  • gu,0/1g_{u, 0 / 1} 代表 uu 的贡献给到了 uuuu 的子树内的最大权值。
  • hu,0/1h_{u, 0 / 1} 代表 uu 的贡献给到了 v(vsonu)v(v\in son_u)uu 的子树内的最大权值。
  • 第二维的 0/10 / 1 代表的是中心符号为正号 / 负号,因为此时 dp 的贡献不只有单个合并,不能直接套用绝对值,但是 x=max(x,x)|x| = \max(x, -x) 刚好满足我们的需求。
  • 需要注意,此时不考虑 fa(u)fa(u) 的贡献。
在 dp 转移的时候,就需要考虑 uu 及其邻域的合并顺序(因为顺序只考虑 uu 及其邻域,所以任意一种顺序都可以逆推出至少一种满足限制的合并顺序)。
对于 fuf_u 的转移:
  • fa(u)fa(u) 一定是 uu 及其邻域中操作最早的点。
  • 对于 v(vsonu)v(v\in son_u)vv 的贡献给到 u,v,w(wsonv)u, v, w(w\in son_v) 都是合法的。
对于 gug_u 的转移:
  • uu 一定是 uu 及其邻域中操作最早的点。
  • 对于 v(vsonu)v(v\in son_u)vv 的贡献只能给到 u,w(wsonv)u, w(w\in son_v)。如果 vv 的贡献也给到 vv,那么 u,vu, v 的顺序无法调配。
对于 huh_u 的转移:
  • 考虑先枚举 vv 代表 vvuu 及其邻域中操作最早的点。
  • 对于 vvvv 的贡献只能给到 v,w(wsonv)v, w(w\in son_v)。如果给到 uu 会无法调配。
  • 对于 v(vsonu,vv)v'(v'\in son_u, v'\not = v)vv' 的贡献能给到 u,v,w(wsonv)u, v', w'(w'\in son_{v'})
对于 hh 的暴力转移是 O(Eu2)\mathcal{O}(\sum |E_u|^2) 的,写出转移后容易优化到 O(n)\mathcal{O}(n)
f,gf, g 转移也均为 O(n)\mathcal{O}(n),最后时间复杂度为 O(n)\mathcal{O}(n)
代码中注释的就是 hh 的暴力转移,第二维为 00 代表邻域为 +1+1,为 11 代表邻域为 1-1
C
#include <bits/stdc++.h>

using ll = long long;
constexpr ll inf = 1e15;

constexpr int maxn = 351493 + 10;

int n;
ll d[maxn];
std::vector<int> son[maxn];

ll f[maxn][2], g[maxn][2], h[maxn][2];
// f : d[u] -> fa
// g : d[u] -> u
// h : d[u] -> v
// ...[][2] : p / n

void dfs(int u) {
    for (int v : son[u]) dfs(v);
    
    // case 1 : d[u] -> fa

    std::array<ll, 2> s = {};
    for (int v : son[u]) {
        s[0] += std::max({f[v][0], g[v][0], g[v][1], h[v][0], h[v][1]});
        s[1] += std::max({f[v][1], g[v][0], g[v][1], h[v][0], h[v][1]});
    }
    f[u][0] = d[u] + std::max(s[0], s[1]), f[u][1] = -d[u] + std::max(s[0], s[1]);

    // case 2 : d[u] -> u

    s = {};
    for (int v : son[u]) {
        s[0] += std::max({f[v][0], h[v][0], h[v][1]});
        s[1] += std::max({f[v][1], h[v][0], h[v][1]});
    }
    g[u][0] = -d[u] + s[0], g[u][1] = d[u] + s[1];

    // case 3 : d[u] -> v

    h[u][0] = h[u][1] = -inf;
    s = {};
    for (int v : son[u]) {
        s[0] += std::max({f[v][0], g[v][0], g[v][1], h[v][0], h[v][1]});
        s[1] += std::max({f[v][1], g[v][0], g[v][1], h[v][0], h[v][1]});
    }
    for (int v : son[u]) {
        s[0] -= std::max({f[v][0], g[v][0], g[v][1], h[v][0], h[v][1]});
        s[1] -= std::max({f[v][1], g[v][0], g[v][1], h[v][0], h[v][1]});
        const ll valv = std::max(d[u] + std::max(g[v][0], h[v][0]), -d[u] + std::max(g[v][1], h[v][1]));
        h[u][0] = std::max(h[u][0], valv + s[0]), h[u][1] = std::max(h[u][1], valv + s[1]);
        s[0] += std::max({f[v][0], g[v][0], g[v][1], h[v][0], h[v][1]});
        s[1] += std::max({f[v][1], g[v][0], g[v][1], h[v][0], h[v][1]});
    }

    // h[u][0] = h[u][1] = -inf;
    // for (int v : son[u]) {
    //     s = {};
    //     for (int w : son[u]) {
    //         if (w == v) continue;
    //         s[0] += std::max({f[w][0], g[w][0], g[w][1], h[w][0], h[w][1]});
    //         s[1] += std::max({f[w][1], g[w][0], g[w][1], h[w][0], h[w][1]});
    //     }

    //     const ll valv = std::max(d[u] + std::max(g[v][0], h[v][0]), -d[u] + std::max(g[v][1], h[v][1]));
    //     h[u][0] = std::max(h[u][0], valv + s[0]), h[u][1] = std::max(h[u][1], valv + s[1]);
    // }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%lld", &d[i]);
    for (int i = 2, x; i <= n; i++) scanf("%d", &x), son[x].push_back(i);

    dfs(1);
    printf("%lld\n", std::max({g[1][0], g[1][1], h[1][0], h[1][1]}));
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...