1 条题解

  • 0
    @ 2025-11-10 17:24:07

    题意理解

    我们有 nn 个人,在 TT 个时刻内有一些预言约束:

    1. 类型 0:在 tt 时刻,如果 xx 死亡,那么 t+1t+1 时刻 yy 死亡

      • 逻辑等价:若 yyt+1t+1 时刻存活,则 xxtt 时刻必须存活
      • 形式化:dead(x,t)    dead(y,t+1)dead(x,t) \implies dead(y,t+1)
        等价于 alive(y,t+1)    alive(x,t)alive(y,t+1) \implies alive(x,t)
    2. 类型 1:在 tt 时刻,如果 xx 存活,那么 tt 时刻 yy 死亡

      • 逻辑等价:若 yytt 时刻存活,则 xxtt 时刻必须死亡
      • 形式化:alive(x,t)    dead(y,t)alive(x,t) \implies dead(y,t)
        等价于 alive(y,t)    dead(x,t)alive(y,t) \implies dead(x,t)

    我们要对每个人 kk,计算有多少个其他人 iikk 同时存活到 T+1T+1 时刻

    关键难点

    1. 时间维度TT 可能很大(最大 10610^6),不能直接对每个时刻建模
    2. 成对判断:需要判断每对人 (i,j)(i,j) 是否能同时存活
    3. 死亡不可逆:一旦死亡,之后时刻都是死亡状态

    核心思路

    1. 转化为图论问题

    我们可以将「人在某个时刻存活/死亡」看作布尔变量,预言就是逻辑蕴含关系。

    对于类型 0:alive(y,t+1)    alive(x,t)alive(y,t+1) \implies alive(x,t)
    对于类型 1:alive(y,t)    dead(x,t)alive(y,t) \implies dead(x,t)
    dead(x,t)dead(x,t) 等价于 ¬alive(x,t)\neg alive(x,t),所以类型 1 实际上是:
    alive(y,t)    ¬alive(x,t)alive(y,t) \implies \neg alive(x,t)

    这提示我们可以建立 2-SAT 模型,但时间维度很大,需要优化。

    2. 时间压缩

    观察发现,我们只关心最终时刻 T+1T+1 的存活状态。但预言在不同时间点建立了存活状态之间的约束。

    关键技巧:如果 ii 要在 T+1T+1 存活,那么根据预言,某些人在某些时间必须存活或死亡

    实际上,我们可以从最终时刻倒推,确定为了让某人最终存活,哪些时刻的哪些人必须存活/死亡。

    3. 成对可行性判断

    对于两个人 iijj,要判断他们是否能同时活到 T+1T+1,我们需要检查是否存在一种生死安排,满足:

    • 所有预言都为真
    • iiT+1T+1 存活
    • jjT+1T+1 存活

    这等价于:将 alive(i,T+1)=truealive(i,T+1)=truealive(j,T+1)=truealive(j,T+1)=true 加入约束系统,检查是否矛盾。

    4. 图的建立与可达性

    我们可以建立这样一个有向图:

    • 每个节点表示 (person,time,state)(person, time, state),其中 state=0state=0 表示死亡,state=1state=1 表示存活
    • 但时间可能很大,需要优化

    实际上,由于死亡不可逆,如果 iitt 时刻死亡,那么之后所有时刻都死亡。所以我们可以只关心存活的传播。

    更精妙的建图方法:

    • 对每个预言建立边:
      • 类型 0:从 alive(y,t+1)alive(y,t+1) 连向 alive(x,t)alive(x,t)
      • 类型 1:从 alive(y,t)alive(y,t) 连向 alive(x,t)alive(x,t) 的否定(即 dead(x,t)dead(x,t)
    • dead(x,t)dead(x,t) 意味着之后所有时刻都死亡,所以可以传播

    最终我们只需要判断:如果要求 iijj 都最终存活,是否会通过约束推导出矛盾。

    5. 实际算法

    1. 建立约束图

      • 对类型 0:alive(y,t+1)alive(x,t)alive(y,t+1) \rightarrow alive(x,t)
      • 对类型 1:alive(y,t)dead(x,t)alive(y,t) \rightarrow dead(x,t)
        dead(x,t)dead(x,t) 意味着 tt,dead(x,t)\forall t' \ge t, dead(x,t'),特别是 dead(x,T+1)dead(x,T+1)
    2. 矛盾判断
      如果存在路径从 alive(i,T+1)alive(i,T+1)dead(i,T+1)dead(i,T+1),或者从 alive(j,T+1)alive(j,T+1)dead(j,T+1)dead(j,T+1),或者从 alive(i,T+1)alive(i,T+1)dead(j,T+1)dead(j,T+1) 等,则 iijj 不能同时存活。

    3. 优化
      由于 nn 不大(最多 3×1043\times 10^4),我们可以对每个人 ii,计算「如果 ii 最终存活,那么哪些人必须最终死亡」。
      然后对于每对 (i,j)(i,j),检查是否 ii 的存活导致 jj 死亡,或者 jj 的存活导致 ii 死亡。

    6. 具体实现

    • nn 个点表示「某人最终存活」
    • 根据预言建立传递关系:
      • 类型 0:如果 yyt+1t+1 存活,则 xxtt 存活。由于存活具有连续性(如果最终存活,那么之前所有时刻都存活),所以 yy 最终存活 \rightarrow xxtt 存活 \rightarrow xx 最终存活
      • 类型 1:如果 yytt 存活,则 xxtt 死亡 \rightarrow xx 最终死亡

    但类型 1 的 tt 可能不是最终时刻,需要传播死亡关系。

    实际上,更简单的方法:
    n×nn\times n 的矩阵 cannot[i][j]cannot[i][j] 表示 ii 存活是否导致 jj 死亡

    初始化:cannot[i][i]=truecannot[i][i] = true(自己不能导致自己死亡?不对,应该是 cannot[i][i]cannot[i][i] 表示矛盾,即 ii 存活导致 ii 死亡,这是不可能的,所以初始为 false)

    我们建立约束:

    • 类型 0:无直接导致死亡的关系
    • 类型 1:alive(y,t)alive(y,t)alive(x,T+1)alive(x,T+1) 矛盾,因为 alive(x,T+1)alive(x,T+1) 意味着 alive(x,t)alive(x,t),但类型 1 要求 alive(y,t)    dead(x,t)alive(y,t) \implies dead(x,t)

    所以:如果 yytt 存活且 xx 最终存活,则矛盾。
    yy 最终存活且 xx 最终存活 不可能同时成立。

    因此类型 1 预言直接给出:yyxx 不能同时最终存活。

    最终算法

    1. 初始化 cannotcannotn×nn\times n 的布尔矩阵,全部为 false
    2. 对于每个类型 1 预言 (t,x,y)(t,x,y)
      • cannot[y][x]=truecannot[y][x] = true(如果 yy 存活,则 xx 必须死亡)
      • cannot[x][y]=truecannot[x][y] = true(如果 xx 存活,则 yy 必须死亡)
    3. 对于每个类型 0 预言 (t,x,y)(t,x,y)
      • 这表示如果 yyt+1t+1 存活,则 xxtt 存活
      • 但我们需要传播:如果 yy 最终存活,那么 xxtt 存活,这没有直接导致死亡关系
      • 实际上类型 0 不会直接导致「不能共存」
    4. 用 Floyd-Warshall 传递闭包:
      如果 ii 存活导致 kk 死亡,且 kk 存活导致 jj 死亡,则 ii 存活导致 jj 死亡
    5. 对每个人 ii,计算能与其共存的人数:
      ans[i]=n1ji[cannot[i][j]]ans[i] = n - 1 - \sum_{j\neq i} [cannot[i][j]]

    时间复杂度

    • Floyd-Warshall: O(n3)O(n^3)
    • 对于 n3×104n \le 3\times 10^4 不可行,需要优化

    优化

    实际上,我们只需要对每个人 ii,找出所有「如果 ii 存活,则必须死亡」的人 jj

    这可以通过从 ii 开始 BFS 得到,复杂度 O(n(n+m))O(n(n+m)),对于 n=30000,m=60000n=30000, m=60000 仍然太大。

    进一步优化:用 bitset 加速 BFS,复杂度 O(n(n+m)/w)O(n(n+m)/w),在 n=30000n=30000 时可行。

    代码框架

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 30005;
    vector<int> g[N];  // g[i]: 如果i存活,则这些j必须死亡
    bitset<N> die[N];  // die[i]: 如果i存活,则这些j必须死亡
    
    int main() {
        int T, n, m;
        cin >> T >> n >> m;
        
        vector<tuple<int,int,int>> type1;
        vector<tuple<int,int,int>> type0;
        
        for (int i = 0; i < m; i++) {
            int c, t, x, y;
            cin >> c >> t >> x >> y;
            if (c == 0) {
                type0.emplace_back(t, x, y);
            } else {
                type1.emplace_back(t, x, y);
                // 直接建立互斥关系
                g[y].push_back(x);
                g[x].push_back(y);
            }
        }
        
        // 对类型0预言,建立时间上的依赖关系
        // 这里需要更复杂的处理,但核心是类型1直接给出互斥
        
        // 用BFS+bitset优化
        for (int i = 1; i <= n; i++) {
            die[i].set(i);  // 自己不能与自己共存?实际上这里表示必须死亡,自己不用
            queue<int> q;
            q.push(i);
            while (!q.empty()) {
                int u = q.front(); q.pop();
                for (int v : g[u]) {
                    if (!die[i][v]) {
                        die[i].set(v);
                        q.push(v);
                    }
                }
            }
        }
        
        for (int i = 1; i <= n; i++) {
            int cnt = 0;
            for (int j = 1; j <= n; j++) {
                if (i != j && !die[i][j]) cnt++;
            }
            cout << cnt << " ";
        }
        
        return 0;
    }
    

    总结

    这道题的关键是将生死预言转化为逻辑约束,通过图论模型判断两个人是否能同时存活。虽然时间维度很大,但通过分析预言类型和存活/死亡的传播性质,可以简化为最终时刻的共存性判断,用传递闭包或 BFS+bitset 高效求解。

    • 1

    信息

    ID
    5150
    时间
    10000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    11
    已通过
    1
    上传者