Skip to content
Hardy Hardy

文章

题解:P7727 风暴之眼(Eye of the Storm)

题解 17 0 0

思路

由题意可以很直观地想到,直接根据 4 种初始状态设计转移,分别对应 (1,\texttt{OR})(0,\texttt{OR})(1,\texttt{AND})(0,\texttt{AND})

但是这样设计时,对于目标值为 01 的结点需要分别设计转移,比较复杂。

把最终目标值一并考虑后,可以将这些情况统一归纳为 5 个状态(其中包含 1 个非法状态)。这里的状态指的是初始值相对于目标值所扮演的角色。

  1. 脆弱态

    结点当前已经是目标值,但自身不能保证一直维持该值。只要受到相反权值的邻居影响(后文称作污染),就会偏离目标值。

    • 当目标值 a_u=0 时:

      (w_u,t_u)=(0,\texttt{OR})

      此时只要某个邻居出现 1,结点 u 就会变为 1

    • 当目标值 a_u=1 时:

      (w_u,t_u)=(1,\texttt{AND})

      此时只要某个邻居出现 0,结点 u 就会变为 0

    因此,状态 0 的结点不能处于异色连通块的边界,其所有邻居的目标值都必须与它相同。即使邻居的目标值相同,也必须保证其初始值不会在传播过程中污染 u,这一限制会体现在后面的转移中。

  2. 渴求态

    结点的初始值与目标值不同,需要某个邻居向它传递正确的权值,但其子树中暂时不存在这样的邻居,只能依靠父亲方向将它转变为目标值。

    • 当目标值 a_u=0 时:

      (w_u,t_u)=(1,\texttt{AND})

      结点 u 需要某个邻居变为 0,才能通过 \texttt{AND} 运算变为 0

    • 当目标值 a_u=1 时:

      (w_u,t_u)=(0,\texttt{OR})

      结点 u 需要某个邻居变为 1,才能通过 \texttt{OR} 运算变为 1

  3. 满足态

    状态 2 与状态 1 对应的初始值和类型相同,区别在于其子树中已经存在一个能够向它传递目标值的结点,因此不再需要父亲提供帮助。

    一旦结点变为目标值,由于它的运算包含自身,它之后便不会再次偏离目标值。

    • 当目标值 a_u=0 时:

      (w_u,t_u)=(1,\texttt{AND})

      子树中已经存在能够向 u 传递 0 的结点。

    • 当目标值 a_u=1 时:

      (w_u,t_u)=(0,\texttt{OR})

      子树中已经存在能够向 u 传递 1 的结点。

  4. 强源态

    结点初始时就是目标值,并且无论邻居如何变化,它都不会偏离目标值,还可以持续向相邻结点传播正确的权值。

    • 当目标值 a_u=0 时:

      (w_u,t_u)=(0,\texttt{AND})

      由于 \texttt{AND} 运算包含自身,而自身已经为 0,所以它之后始终为 0

    • 当目标值 a_u=1 时:

      (w_u,t_u)=(1,\texttt{OR})

      由于 \texttt{OR} 运算包含自身,而自身已经为 1,所以它之后始终为 1

  5. 非法态(不计入 DP)

    对于不同的目标值,还各存在一种不可能达到目标值的初始状态,因此不计入任何 DP 状态。

    • 当目标值 a_u=0 时,(w_u,t_u)=(1,\texttt{OR}) 非法。

      由于运算包含结点自身,而 u 的初始值为 1,所以每次进行 \texttt{OR} 运算后它都仍然为 1,永远不可能变为目标值 0

    • 当目标值 a_u=1 时,(w_u,t_u)=(0,\texttt{AND}) 非法。

      由于运算包含结点自身,而 u 的初始值为 0,所以每次进行 \texttt{AND} 运算后它都仍然为 0,永远不可能变为目标值 1


故可以设 dp[u][0\ldots 3] 表示以 u 为根的子树中,使得 u 处于对应状态的合法方案数。

在转移过程中,我们按顺序将结点 u 的各个儿子合并进来。设当前正在合并的儿子为 v

  • u_i:在合并儿子 v 之前,结点 u 已经处理完的部分中,u 处于状态 i 的方案数。
  • v_i:以结点 v 为根的完整子树中,v 处于状态 i 的方案数。
  • n_i:将儿子 v 合并进来之后,结点 u 处于状态 i 的新方案数。

这里的下标 i\in\{0,1,2,3\} 对应前文定义的四种状态。

每次完成转移后,将新值赋回:

u_i\leftarrow n_i\qquad(0\le i<4)

转移需要根据边 (u,v) 两端的最终目标 a_ua_v 是否相同分类讨论。

同色边

a_u=a_v 时,两端的目标值相同,正确权值可以沿着同色连通块逐步传播。

  • n_0=u_0\times(v_0+v_3)

    u 处于脆弱态,因此 v 必须从一开始就保持与目标值相同。合法的状态只有同样脆弱的 v_0 和稳定的强源态 v_3

  • n_1=u_1\times v_1

    u 尚未被满足,因此 v 也不能向 u 提供目标值,只能处于状态 v_1

    u 之后从父亲方向获得满足时,正确权值还可以继续由 u 传递给 v

  • n_2=u_1\times(v_2+v_3)+u_2\times(v_1+v_2+v_3)

    u 被满足有两种途径。

    第一种是 u 原本处于渴求态 u_1,并从已经满足的 v_2 或强源态 v_3 处获得目标值。

    第二种是 u 原本已经处于满足态 u_2。此时 v 只要不是脆弱态 v_0 即可,因为 u_2 的初始值与目标值相反,会在第一秒污染 v_0

  • n_3=u_3\times(v_0+v_1+v_2+v_3)

    u 是稳定的强源态,不会被 v 改变,并且还能向需要满足的 v_1 传播正确的权值,因此 v 可以处于任意状态。

异色边

a_u\ne a_v 时,两端的最终目标值相反。

异色边既要考虑两端初始值在第一秒产生的影响,也要保证结点稳定后不会继续污染对方的脆弱态。

由于 a_u\ne a_v,所以 v_1v_2 的初始值与 a_v 相反,恰好等于 a_u;而 v_0v_3 的初始值等于 a_v,与 a_u 相反。

  • n_0=0

    v 最终会稳定为 a_v,而 a_v\ne a_u。因此 u 迟早会受到相反权值的污染,脆弱态一定非法。

  • n_1=u_1\times v_3

    v 处于 v_1v_2,其初始值恰好等于 a_u,会直接满足 u,结果应转入状态 2。

    v 处于脆弱态 v_0,当 u 之后从父亲处获得满足并变为 a_u 时,会反过来污染 v_0

    因此,只有稳定且不会满足 u 的强源态 v_3 合法。

  • n_2=u_1\times(v_1+v_2)+u_2\times(v_1+v_2+v_3)

    u 原本处于渴求态 u_1 时,v_1v_2 的初始值恰好等于 a_u,可以在第一秒满足 u

    u 原本已经处于满足态 u_2 时,v_0 会在 u 变为目标值后受到污染,因此不合法;v_1v_2v_3 均可以与 u 共存。

  • n_3=u_3\times(v_2+v_3)

    u_3 的初始值和最终值均为 a_u,与 a_v 相反。

    v 处于脆弱态 v_0,会立即被 u_3 污染;若 v 处于渴求态 v_1,其子树无法满足它,而 u_3 也无法向它提供目标值 a_v

    因此,v 只能处于已经被子树满足的 v_2 或稳定的强源态 v_3

所有儿子合并完成后,若根结点仍处于状态 1,它已经没有父亲可以依赖,因此这种方案不合法。最终答案为:

dp[1][0]+dp[1][2]+dp[1][3]

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;
const int mod = 998244353;

int n, a[N];
vector<int> g[N];
bool b[N];
ll dp[N][4];

void dfs(int u, int p)
{
    // 异色边界上的结点不能处于状态 0
    dp[u][0] = b[u] ? 0 : 1;
    dp[u][1] = 1;
    dp[u][2] = 0;//表示已经被子树满足,一开始还没有子树信息,因此初始方案数为 $0$
    dp[u][3] = 1;

    for (int v : g[u])
    {
        if (v == p)
            continue;

        dfs(v, u);

        ll v0 = dp[v][0], v1 = dp[v][1];
        ll v2 = dp[v][2], v3 = dp[v][3];
        ll n0, n1, n2, n3;

        if (a[u] == a[v])
        {
            n0 = dp[u][0] * (v0 + v3) % mod;
            n1 = dp[u][1] * v1 % mod;
            n2 = (dp[u][1] * (v2 + v3) % mod
                + dp[u][2] * (v1 + v2 + v3) % mod) % mod;
            n3 = dp[u][3] * (v0 + v1 + v2 + v3) % mod;
        }
        else
        {
            n0 = 0;
            n1 = dp[u][1] * v3 % mod;
            n2 = (dp[u][1] * (v1 + v2) % mod
                + dp[u][2] * (v1 + v2 + v3) % mod) % mod;
            n3 = dp[u][3] * (v2 + v3) % mod;
        }

        dp[u][0] = n0;
        dp[u][1] = n1;
        dp[u][2] = n2;
        dp[u][3] = n3;
    }
}

int main()
{
    cin.tie(0)->sync_with_stdio(0);

    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    // 标记异色连通块的边界
    for (int u = 1; u <= n; u++)
    {
        for (int v : g[u])
        {
            if (a[u] != a[v])
            {
                b[u] = true;
                break;
            }
        }
    }

    dfs(1, 0);

    // 根结点不能处于尚未满足的状态 1
    ll ans = (dp[1][0] + dp[1][2] + dp[1][3]) % mod;
    cout << ans << "\n";

    return 0;
}