1 条题解

  • 0
    @ 2025-11-11 8:30:00

    题意理解 我们有一个有向图:

    n n 个点, 2 m 2m 条边(因为每条“左括号边”都对应一条反向的“右括号边”)

    每条边标记为某种括号类型的左括号 ( 或右括号 )

    括号类型有 k k 种

    要求统计点对 ( x , y ) (x,y)( x < y x<y)的数量,使得存在一条从 x x 到 y y 的路径,路径上边的标记按顺序组成一个合法的括号序列。

    合法括号序列条件 一个合法括号序列需要满足:

    左右括号数量相等

    任意前缀中,左括号数量 ≥ 右括号数量

    初步分析 直接枚举点对和路径不可行( n n 可达 3 × 10 5 3×10 5 )。

    我们需要利用括号序列的性质来设计高效算法。

    括号序列与栈 一个常见的技巧是:遇到左括号压栈,遇到右括号时,如果栈顶是匹配的左括号则弹出,否则非法。

    在图上,我们可以把“栈”的状态设计进图搜索中。

    状态表示 设状态为 ( u , S ) (u,S),其中:

    u u 是当前节点

    S S 是当前栈的内容(表示还未匹配的左括号序列)

    但栈可能很长,直接存储不可行。

    关键观察 由于括号类型有限( k ≤ n k≤n),我们可以用一个 k k 元组表示栈: 栈中只关心每种括号未匹配的左括号数量。

    因为匹配必须是同种括号的左右括号匹配,不同种类括号互不影响。

    所以栈状态可以表示为一个长度 k k 的数组 c n t [ 1.. k ] cnt[1..k], c n t [ w ] cnt[w] 表示第 w w 种括号未匹配的左括号数量。

    状态转移 从状态 ( u , c n t ) (u,cnt) 出发:

    如果走一条左括号边 u → v u→v,类型 w w,则 c n t ′ [ w ]

    c n t [ w ] + 1 cnt ′ [w]=cnt[w]+1,其他不变。

    如果走一条右括号边 u → v u→v,类型 w w,则:

    如果 c n t [ w ]

    0 cnt[w]>0,则 c n t ′ [ w ]

    c n t [ w ] − 1 cnt ′ [w]=cnt[w]−1,其他不变(合法匹配)

    如果 c n t [ w ]

    0 cnt[w]=0,则此转移非法(右括号无法匹配)

    初始状态: ( u , z e r o ) (u,zero),其中 z e r o zero 是全 0 数组。

    目标状态: ( v , z e r o ) (v,zero) 表示栈为空,即完整匹配的括号序列。

    问题转化 我们要求的是:对于所有 x < y x<y,是否存在从 x x 到 y y 的路径,使得从空栈开始,结束时空栈。

    即:从 ( x , z e r o ) (x,zero) 能否到达 ( y , z e r o ) (y,zero)。

    图规模问题 状态数是 n × ( L + 1 ) k n×(L+1) k ,其中 L L 是栈的最大深度。但 L L 可能很大,直接 BFS 状态爆炸。

    进一步优化 注意到我们只关心栈为空的状态,因此我们可以考虑双向搜索:

    从起点 x x 出发,记录能到达的状态 ( u , c n t ) (u,cnt) 从终点 y y 出发(在反向图上),记录能到达的状态 ( u , c n t ) (u,cnt) 如果存在某个中间节点 u u,使得正向能到达 ( u , S ) (u,S),反向能到达 ( u , S ) (u,S),那么 x → y x→y 存在合法路径。

    但这样还是需要枚举点对,不行。

    官方解法思路(分治 + 最短路径) 这是一个已知题,常用做法是分治 + Dijkstra:

    我们给每个节点 u u 分配一个 k k 维向量 d i s t [ u ] dist[u],表示从某个起点出发到达 u u 时的最小“栈状态”(用某种度量表示)。

    利用分治,每次处理起点在某一半,终点在另一半的点对。

    在分治过程中,对起点集合进行多源 Dijkstra,状态为 ( u , c n t ) (u,cnt),但通过巧妙的状态表示,可以避免指数级状态数。

    实际上,因为不同括号类型独立,我们可以把问题分解为 k k 个独立的括号匹配问题吗? 不行,因为路径上的括号顺序是混合的。

    更可行的思路:BFS 状态压缩 由于 k ≤ n k≤n,但 n n 很大,不能直接指数枚举。 但数据范围中 k k 可能较小(测试点里有的 k

    1 , 2 k=1,2),对于 k k 小的情况可以直接 BFS 状态 ( u , c n t ) (u,cnt)。

    对于 k k 大的情况,需要利用图的性质。

    另一种思路:转化为括号树 把括号序列看作树结构:

    左括号:进入新子树

    右括号:返回父节点

    在图上,我们可以建立“括号自动机”来跟踪栈状态,然后用可达性分析。

    实现方案(针对中小 k k) 对于 k ≤ 2 k≤2 的情况,可以直接 BFS 状态 ( u , c 1 , c 2 ) (u,c 1 ​ ,c 2 ​ )。

    算法步骤:

    枚举起点 x x

    BFS 从 ( x , 0 , 0 , . . . , 0 ) (x,0,0,...,0) 开始

    如果到达 ( y , 0 , 0 , . . . , 0 ) (y,0,0,...,0) 且 y

    x y>x,则计数

    用 visited 数组避免重复搜索同一状态

    复杂度: O ( n ⋅ ( L + 1 ) k ⋅ degree ) O(n⋅(L+1) k ⋅degree),对于 k k 小且图稀疏时可接受。

    代码框架( k

    1 k=1 情况示例) cpp #include <bits/stdc++.h> using namespace std;

    int n, m, k; vector<pair<int, int>> adj[300005]; // to, type (0=left, 1=right)

    int main() { cin >> n >> m >> k; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; // 左括号边 u->v adj[u].push_back({v, w}); // 正数表示左括号 // 右括号边 v->u
    adj[v].push_back({u, -w}); // 负数表示右括号 }

    long long ans = 0;
    // 枚举起点
    for (int start = 1; start <= n; start++) {
        // BFS 状态 (node, balance)
        queue<pair<int, int>> q;
        map<pair<int, int>, bool> vis;
        
        q.push({start, 0});
        vis[{start, 0}] = true;
        
        while (!q.empty()) {
            auto [u, bal] = q.front(); q.pop();
            
            if (bal == 0 && u > start) {
                ans++; // 找到合法点对 (start, u)
            }
            
            for (auto [v, type] : adj[u]) {
                int new_bal = bal;
                if (type > 0) { // 左括号
                    new_bal++;
                } else { // 右括号
                    if (new_bal > 0) {
                        new_bal--;
                    } else {
                        continue; // 不合法
                    }
                }
                
                if (!vis.count({v, new_bal})) {
                    vis[{v, new_bal}] = true;
                    q.push({v, new_bal});
                }
            }
        }
    }
    
    cout << ans << endl;
    return 0;
    

    } 大规模数据优化 对于 n n 大的情况,需要更高效的方法:

    利用强连通分量缩点

    动态规划计算可达性

    分治处理点对

    这类题目的正解通常比较复杂,需要结合括号自动机和图论优化。

    总结 这道题的核心是:

    将括号匹配的栈状态融入图搜索

    状态表示为 ( u , c n t 1 , . . . , c n t k ) (u,cnt 1 ​ ,...,cnt k ​ ),利用 k k 较小的特点

    通过 BFS/DFS 统计合法点对

    对于大数据需要更复杂的优化(缩点、分治、DP)

    由于时间和篇幅限制,这里给出了 k k 较小时的可行方案,大规模数据需要进一步优化。

    • 1

    信息

    ID
    4940
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者