[R41E]颜色题


数据规模:1≤n≤1051 \le n \le 10^5,1≤ci≤101 \le c_i \le 10,删边序列是 1..n−11..n-1 的排列。

思路

设 NcN_c 为「路径上至少经过一条颜色为 cc 的边」的点对数。答案就是

∏c=110cNc mod 998244353\prod_{c=1}^{10} c^{N_c} \bmod 998244353

因为同一颜色在一条路径上只贡献一次,点对 (x,y)(x,y) 的 f(x,y)f(x,y) 对答案的贡献恰好是把路径上出现过的每种颜色各乘一次。

于是问题变成维护每个颜色 cc 的 NcN_c。删边是持久的,正难则反,把删边倒过来看成加边:初始是没有边的森林(答案为 11),按 xn−1,xn−2,…,x1x_{n-1}, x_{n-2}, \ldots, x_1 的顺序把边加回去。

考虑加回一条边 (u,v)(u,v)(颜色 cc),它把两个连通块 A,BA,B 合并。新产生的连通点对数为 P=∣A∣×∣B∣P=|A|\times|B|,这些点对的路径都经过新边。对颜色 dd 分两类:

  • d=cd = c:新边本身就是颜色 cc,所有 PP 个新点对的路径都含颜色 cc,故 ΔNc=P\Delta N_c = P。
  • d≠cd \ne c:新点对中路径不含颜色 dd 的,恰好是「uu 侧不含颜色 dd 边的点」与「vv 侧不含颜色 dd 边的点」的配对。若我们维护一个只含非颜色 dd 边的并查集,那么 uu 侧这类点的个数就是该并查集中 uu 所在连通块的大小 sd(u)s_d(u)。于是 ΔNd=P−sd(u)⋅sd(v)\Delta N_d = P - s_d(u)\cdot s_d(v)。

所以对每个颜色 dd 各维护一个并查集(其中只有颜色 ≠d\ne d 的边),加边 (u,v,c)(u,v,c) 时:

  • 对 d=cd=c:ΔNd=P\Delta N_d = P;
  • 对 d≠cd\ne c:在颜色 dd 的并查集中合并 u,vu,v(合并前大小乘积即不含颜色 dd 的新点对数),ΔNd=P−sd(u)sd(v)\Delta N_d = P - s_d(u)s_d(v)。

再把答案乘上 ∏ddΔNd\prod_d d^{\Delta N_d} 即可。真实森林也要一个并查集来维护连通块大小 ∣A∣,∣B∣|A|,|B|。

复杂度

每个颜色一个并查集,总大小 O(10n)O(10n);每次加边对 1010 种颜色各做常数次查询/合并,并做一次快速幂。总时间 O(10nα(n)+10nlog⁡MOD)O(10n\alpha(n) + 10n\log MOD),空间 O(10n)O(10n),可以轻松通过 n=105n=10^5。

仓颉实现

查看仓颉实现
import std.convert.*
import std.env.*

const MOD: Int64 = 998244353

func powmod(a: Int64, e: Int64): Int64 {
    var base = a % MOD
    var exp = e
    var res: Int64 = 1
    while (exp > 0) {
        if ((exp & 1) == 1) {
            res = res * base % MOD
        }
        base = base * base % MOD
        exp >>= 1
    }
    return res
}

// 并查集,数组按 base 偏移存放某一颜色对应的父指针
func find(fa: Array<Int64>, base: Int64, r: Int64): Int64 {
    var cur = r
    while (fa[base + cur] != cur) {
        cur = fa[base + cur]
    }
    var w = r
    while (fa[base + w] != w) {
        let nx = fa[base + w]
        fa[base + w] = cur
        w = nx
    }
    return cur
}

main() {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    if (n == 1) {
        println("1")
        return
    }
    let eu = Array<Int64>(n, { _ => 0 })
    let ev = Array<Int64>(n, { _ => 0 })
    let ec = Array<Int64>(n, { _ => 0 })
    for (i in 1..n) {
        let p = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
        eu[i] = p[0]
        ev[i] = p[1]
        ec[i] = p[2]
    }
    let x = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })

    // 真实森林的并查集
    let far = Array<Int64>(n + 1, { _ => 0 })
    let szr = Array<Int64>(n + 1, { _ => 1 })
    // 每种颜色一个并查集:只含非该颜色的边
    let fc = Array<Int64>(11 * (n + 1), { _ => 0 })
    let szc = Array<Int64>(11 * (n + 1), { _ => 1 })
    for (i in 1..(n + 1)) {
        far[i] = i
        for (d in 1..11) {
            fc[d * (n + 1) + i] = i
        }
    }

    let ansArr = Array<Int64>(n, { _ => 1 })
    var ans: Int64 = 1
    var k = n - 2
    while (k >= 0) {
        let e = x[k]
        let u = eu[e]
        let v = ev[e]
        let c = ec[e]
        let ra = find(far, 0, u)
        let rb = find(far, 0, v)
        let P = szr[ra] * szr[rb]
        for (d in 1..11) {
            var dn: Int64 = 0
            if (d == c) {
                dn = P
            } else {
                let base = d * (n + 1)
                let da = find(fc, base, u)
                let db = find(fc, base, v)
                let newPairs = szc[base + da] * szc[base + db]
                if (szc[base + da] < szc[base + db]) {
                    fc[base + da] = db
                    szc[base + db] = szc[base + db] + szc[base + da]
                } else {
                    fc[base + db] = da
                    szc[base + da] = szc[base + da] + szc[base + db]
                }
                dn = P - newPairs
            }
            if (dn > 0) {
                ans = ans * powmod(d, dn) % MOD
            }
        }
        // 真实并查集合并
        if (szr[ra] < szr[rb]) {
            far[ra] = rb
            szr[rb] = szr[rb] + szr[ra]
        } else {
            far[rb] = ra
            szr[ra] = szr[ra] + szr[rb]
        }
        ansArr[k] = ans
        k -= 1
    }

    for (i in 0..n) {
        if (i > 0) {
            print(" ")
        }
        print(ansArr[i])
    }
    println()
}