1 条题解
-
0
题意理解
我们有 个人,在 个时刻内有一些预言约束:
-
类型 0:在 时刻,如果 死亡,那么 时刻 死亡
- 逻辑等价:若 在 时刻存活,则 在 时刻必须存活
- 形式化:
等价于
-
类型 1:在 时刻,如果 存活,那么 时刻 死亡
- 逻辑等价:若 在 时刻存活,则 在 时刻必须死亡
- 形式化:
等价于
我们要对每个人 ,计算有多少个其他人 能与 同时存活到 时刻。
关键难点
- 时间维度: 可能很大(最大 ),不能直接对每个时刻建模
- 成对判断:需要判断每对人 是否能同时存活
- 死亡不可逆:一旦死亡,之后时刻都是死亡状态
核心思路
1. 转化为图论问题
我们可以将「人在某个时刻存活/死亡」看作布尔变量,预言就是逻辑蕴含关系。
对于类型 0:
对于类型 1:
而 等价于 ,所以类型 1 实际上是:
这提示我们可以建立 2-SAT 模型,但时间维度很大,需要优化。
2. 时间压缩
观察发现,我们只关心最终时刻 的存活状态。但预言在不同时间点建立了存活状态之间的约束。
关键技巧:如果 要在 存活,那么根据预言,某些人在某些时间必须存活或死亡
实际上,我们可以从最终时刻倒推,确定为了让某人最终存活,哪些时刻的哪些人必须存活/死亡。
3. 成对可行性判断
对于两个人 和 ,要判断他们是否能同时活到 ,我们需要检查是否存在一种生死安排,满足:
- 所有预言都为真
- 在 存活
- 在 存活
这等价于:将 和 加入约束系统,检查是否矛盾。
4. 图的建立与可达性
我们可以建立这样一个有向图:
- 每个节点表示 ,其中 表示死亡, 表示存活
- 但时间可能很大,需要优化
实际上,由于死亡不可逆,如果 在 时刻死亡,那么之后所有时刻都死亡。所以我们可以只关心存活的传播。
更精妙的建图方法:
- 对每个预言建立边:
- 类型 0:从 连向
- 类型 1:从 连向 的否定(即 )
- 但 意味着之后所有时刻都死亡,所以可以传播
最终我们只需要判断:如果要求 和 都最终存活,是否会通过约束推导出矛盾。
5. 实际算法
-
建立约束图:
- 对类型 0:
- 对类型 1:
而 意味着 ,特别是
-
矛盾判断:
如果存在路径从 到 ,或者从 到 ,或者从 到 等,则 和 不能同时存活。 -
优化:
由于 不大(最多 ),我们可以对每个人 ,计算「如果 最终存活,那么哪些人必须最终死亡」。
然后对于每对 ,检查是否 的存活导致 死亡,或者 的存活导致 死亡。
6. 具体实现
- 用 个点表示「某人最终存活」
- 根据预言建立传递关系:
- 类型 0:如果 在 存活,则 在 存活。由于存活具有连续性(如果最终存活,那么之前所有时刻都存活),所以 最终存活 在 存活 最终存活
- 类型 1:如果 在 存活,则 在 死亡 最终死亡
但类型 1 的 可能不是最终时刻,需要传播死亡关系。
实际上,更简单的方法:
用 的矩阵 表示 存活是否导致 死亡初始化:(自己不能导致自己死亡?不对,应该是 表示矛盾,即 存活导致 死亡,这是不可能的,所以初始为 false)
我们建立约束:
- 类型 0:无直接导致死亡的关系
- 类型 1: 且 矛盾,因为 意味着 ,但类型 1 要求
所以:如果 在 存活且 最终存活,则矛盾。
即 最终存活且 最终存活 不可能同时成立。因此类型 1 预言直接给出: 和 不能同时最终存活。
最终算法
- 初始化 为 的布尔矩阵,全部为 false
- 对于每个类型 1 预言 :
- (如果 存活,则 必须死亡)
- (如果 存活,则 必须死亡)
- 对于每个类型 0 预言 :
- 这表示如果 在 存活,则 在 存活
- 但我们需要传播:如果 最终存活,那么 在 存活,这没有直接导致死亡关系
- 实际上类型 0 不会直接导致「不能共存」
- 用 Floyd-Warshall 传递闭包:
如果 存活导致 死亡,且 存活导致 死亡,则 存活导致 死亡 - 对每个人 ,计算能与其共存的人数:
时间复杂度
- Floyd-Warshall:
- 对于 不可行,需要优化
优化
实际上,我们只需要对每个人 ,找出所有「如果 存活,则必须死亡」的人 。
这可以通过从 开始 BFS 得到,复杂度 ,对于 仍然太大。
进一步优化:用 bitset 加速 BFS,复杂度 ,在 时可行。
代码框架
#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
- 上传者